Azure AD B2C Web API Using .NET Core 2 - asp.net

I’m trying to call a Web API from a Web App both protected by Azure AD B2C. The App signs in fine with the Azure sign in page. But when I call my [Authorize] endpoint on my API I get a 401 unauthorized response.
I thought this is supposed to work right out of the box using VS2017 and ASP.NET Core 2.1. When I created both apps I specified “Individual User Accounts” for authentication and “Connect to an existing user store in the cloud”. The examples I’ve found seem to be from .NET Core 1 or older and no longer relevant or using deprecated setups.
I have the App in the API access section with read and write scopes in Azure.
How do I successfully authorize my App to call my API?
Here’s my App Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
My App appsettings.json:
{
"AzureAdB2C": {
"Instance": "https://myCompanyPassport.b2clogin.com/tfp/",
"ClientId": "51dde0de-a204-4b67-b890-068846e17ff1",
"ClientSecret": "------------------------",
"CallbackPath": "/signin-oidc",
"Domain": "myCompanyPassport.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_myCompanySignUpSignIn",
"ResetPasswordPolicyId": "B2C_1_myCompanyPasswordReset",
"EditProfilePolicyId": "B2C_1_myCompanyProfile",
"TaskServiceUrl": "https://localhost:44337/",
"ApiIdentifier": "https://myCompanyPassport.onmicrosoft.com/taskapi",
"ReadScope": "read",
"WriteScope": "write"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Here’s my API Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
My API appsettings.json:
{
"AzureAdB2C": {
"Instance": "https://myCompanyPassport.b2clogin.com/tfp/",
"ClientId": "213764b3-8c2a-4bf6-9e69-355495a8f14e",
"ClientSecret": "------------------------",
"Domain": "myCompanyPassport.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_myCompanySignUpSignIn",
"ReadScope": "read",
"WriteScope": "write"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}

Did you try this ( addazureadbearer api )
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));

Related

Authorize for Azure AD groups in asp.net mvc

I am trying to use authorization on specific page views in controllers with [Authorize(Policy = "nameOfPolicy")] but I keep getting "Access denied" even though I have access to the Azure AD group that I have entered in my policy.
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Get the scopes from the configuration (appsettings.json)
var initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
// Add sign-in with Microsoft
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
// Add the possibility of acquiring a token to call a protected web API
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
// Enables controllers and pages to get GraphServiceClient by dependency injection
// And use an in memory token cache
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddDistributedTokenCaches();
services.AddAuthorization(options =>
{
options.AddPolicy("it", policy => policy.RequireClaim("groups", "Azure group ID here"));
});
// Register AadService and PbiEmbedService for dependency injection
services.AddScoped(typeof(AadService))
.AddScoped(typeof(PbiEmbedService))
.AddScoped(typeof(PowerBiServiceApi));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
// Enables a UI and controller for sign in and sign out.
services.AddRazorPages()
.AddMicrosoftIdentityUI();
// Session/cookie variables etc
services.AddDistributedMemoryCache();
services.AddSession();
// Loading appsettings.json in C# Model classes
services.Configure<AzureAd>(Configuration.GetSection("AzureAd"))
.Configure<PowerBI>(Configuration.GetSection("PowerBI"));
// Add the UI support to handle claims challenges
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
And in my controller this is how I try to use the Authorize:
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
[Authorize(Policy = "it")]
public Task<IActionResult> Index()
To authorize for Azure AD groups in asp.net MVC.
I have followed the below steps and able to authorize.
Create an APP in Azure AD and register the App.
Using the ID tokens in authentication for the App.
Set the RedirectionUrl in azure for the App from the authentication tab.
Choose the ASP.Net MVC application from Visual studio templates and install the below NuGet packages.
NuGets
Microsoft.AspNetCore.Authentication.AzureAD.UI
Microsoft.Identity.Web
In Startup.cs class make the below changes to register or configure the services of Authentication
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApp(Configuartion.GetSetion("AzureAd"));
services.AddControllersWithViews();
}
And you need add app.UseAuthentication () method along with app.UseAuthorization() in startup.cs class.
And you need to use the TenantId , ClientId and RedirectionUrl in the Settings.Json file.
appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "Project",
"ClientId": "",
"TenantId": "",
"CallbackPath": "/signin-oidc"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
launchSettings.Json
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:42313",
"sslPort": 44302
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"MVC_APP": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:41222",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Authorize attribute need to be add at the controller level.
[Authorize]
public class HomeController : Controller

How to use Azure AD with OpenId Connect in a iFrame

I've just been looking for hours for a way to call a web app in an iFrame that is protected with OpenId Connect SSO.
Current status is that either the following message appears with the link https://localhost:5001/signin-oidc.
Or the following error message with the link https://localhost:5001 :
If the application is called directly via the browser, then the login screen of Azure appears as desired. Only when I go via the iFrame, nothing works anymore. Even if I open the application first and log in, it does not work with iFrame.
The settings were made with the default template of Rider
csproj
...
<UserSecretsId>SomeGuideWithName</UserSecretsId>
<WebProject_DirectoryAccessLevelKey>0</WebProject_DirectoryAccessLevelKey>
...
App.razor
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)"/>
<FocusOnNavigate RouteData="#routeData" Selector="h1"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
appsettings.json
xxx --> Secreds that are correct.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "xxx.onmicrosoft.com",
"TenantId": "xxx",
"ClientId": "xxx",
"CallbackPath": "/signin-oidc"
}
Program.cs
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using MitAuth.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(
options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
}
);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Generated Cookies
The goal is to have application A (Caller) embed an iFrame that application B (OpenIdConnect protected).
Questions:
Do I need to reconfigure anything in App B?
Do I need to do anything else in App A?
Is there any token or similar that has to be passed on?
I tried to reproduce the scenario in my environment:
I got the same error:
System.Exception: An error was encountered while handling the remote login.
---> System.Exception: OpenIdConnectAuthenticationHandler: message.State is null or empty.
Here due to misconfiguration in callback path I got the error.
In your case check without giving callbackpath in both code and portal.
Appsettings.json:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mytenantxxx.onmicrosoft.com",
"TenantId": "fbxxxxxxxxxxxxf3b0",
"ClientSecret": "xxxx",
"ClientId": "xxxx",
"CallbackPath": "/"
},
I have configured following redirect urls in portal.
My application url uses http protocol http://localhost:44328
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
services.AddRazorPages();
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
services.AddSingleton<WeatherForecastService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
When I changed the CallbackPath value to /signin-oidc , that error was resolved.
Appsettings.json:
"AzureAd": {
...
"CallbackPath": "/signin-oidc"
},
Also check Exception: Correlation failed. AAD + Azure Front Door

How to Access ASP.NET Web API Backend with Mobile Phone Android Browser using Angular Local Host

I am building my first website (total newbie) using Angular and ASP.NET Web API. I can access my web app on localhost 4200 and using 192.168.100.2:4200 on my pc browser.
But when I try to log on the same localhost with a Mobile Phone Android Browser, It returns a server error.
Following are the commands I tried
ng serve --host 192.168.100.2
ng serve --host 192.168.100.2 --disable-host-check
ng serve --host 0.0.0.0
This is my Login method on the Component
login(){
if(this.loginForm.valid){
const record = this.loginForm.getRawValue();
console.log(record);
this.authService.login({
username: record.userName,
password: record.password,
}).subscribe({
next:(data: any) => {
this.authService.authToken = JSON.parse(data).token;
console.log("Orig Token: ", data);
console.log("Parsed Token: ", this.authService.authToken);
console.log("Parsed Token (Subject): ", this.authService.authToken.split('.')[1]);
console.log("JSON (after atob) ",this.authService.atob);
console.log("Object (Payload): ",this.authService.payLoad);
console.log("User id: ",this.authService.userID);
console.log("Name: ",this.authService.userName);
console.log("Modules: ",this.authService.accessibleModulesOfUser);
this.router.navigateByUrl('');
this.toastr.success("Welcome Back.")
},
error: (e) => {
if (e.status == 0) {
// this.msgs.push({ severity: 'warn', detail: 'Server is not available. Please try again later.' });
// this.toastr.error("Server is not available. Please try again later")
this.toastr.error("Server is not available. Please try again later")
}
else if (e.status == ApiCallStatusCodes.UNAUTHORIZED) {
// this.msgs.push({ severity: 'error', detail: err.error });
// console.log(e);
this.toastr.error(e.error)
}
else {
// this.msgs.push({ severity: 'warn', detail: err.error });
// console.log(e);
this.toastr.error(e.error)
}
}
})
}
else{
this.toastr.warning("Enter username and password.")
}
}
I already try adding Inbound Rule to my Windows Defender Firewal but it doesnt work.
Also, I modify the CORS in startup of ASP.NET but still has no luck.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebAPIv5 v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:4200", "http://192.168.100.2:4200"));
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Openiddict with dotnet core 5 giving the errors as "this server only accepts HTTPS requests."

I am trying to use the oidc-client with oppeniddict in the angular application but there is the error with .well-known/openid-configuration.
Error says:
GET http://localhost:2987/.well-known/openid-configuration 400 (Bad Request)
I have the openiddict implementation in the dot-net core 5 application.
Then I grab the URL http://localhost:2987/.well-known/openid-configuration and browse it in the browser, I am getting the error:
{
"error": "invalid_request",
"error_description": "This server only accepts HTTPS requests.",
"error_uri": "https://documentation.openiddict.com/errors/ID2083"
}
I have also disabled the SSL from web server settings as shown in the figure:
My startup ConfigureServices looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"], sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
});
options.UseOpenIddict();
});
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
});
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
}).AddServer(options =>
{
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetLogoutEndpointUris("/connect/logout")
.SetIntrospectionEndpointUris("/connect/introspect")
.SetUserinfoEndpointUris("/connect/userinfo");
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles);
options.AllowImplicitFlow();
options.AddEncryptionKey(new SymmetricSecurityKey(
Convert.FromBase64String("DRjd/GnduI3Efzen9V9BvbNUfc/VKgXltV7Kbk9sMkY=")));
options.AddDevelopmentSigningCertificate();
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
.EnableStatusCodePagesIntegration();
}).AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
services.AddCors(options => options.AddPolicy("ApiCorsPolicy", builder =>
{
builder.WithOrigins("http://localhost:4200").AllowAnyMethod().AllowAnyHeader();
}));
services.AddControllersWithViews();
}
Configure:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStatusCodePagesWithReExecute("/error");
app.UseRouting();
app.UseCors("ApiCorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(options =>
{
options.MapControllers();
options.MapDefaultControllerRoute();
});
}
I feel like I have been missing something that is super easy to do. But couldn't find the actual reason for this. There are not any issues in the StackOverflow with this.
Is it the error from Openiddict or from the dot net core 5 itself? Any guide or workaround will be appreciated to dig out this issue.
I faced this problem recently also.
by default the Openiddict SSL is enable.
if you want to disable ssl checking.
you can disable it via following code
options.UseAspNetCore().DisableTransportSecurityRequirement();
Use Below code in method
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictBuilder>(builder =>
{
builder.AddValidation(options =>
{
options.AddAudiences("PaymentService");
options.UseLocalServer();
options.UseAspNetCore();
});
//below code needs to be added
builder.AddServer(options => { options.UseAspNetCore().DisableTransportSecurityRequirement(); });
});
}

Log to Console with .Net Core 2.0 web application

I'm giving my first steps with .Net Core
Just created a web Hello world with
dotnet new web
I can see there's some kind of logging enabled. I just want to log something to the Console.
But I don't know how to access the logger from
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!!!");
});
I tried with Console.WriteLine but it obviously didn't work.
Also tried with NLog following this guide https://github.com/NLog/NLog.Web/wiki/Getting-started-with-ASP.NET-Core-(csproj---vs2017) but I don't know how to inject the logger.
I'm just trying to look around for educational purposes, not looking for a real logger, so perhaps there's a better/easier option.
I could achieve it with this:
[...]
using Microsoft.Extensions.Logging;
[...]
namespace web
{
public class Startup
{
ILogger log;
public Startup(ILoggerFactory loggerFactory)
{
log = loggerFactory.CreateLogger("Logger");
}
[...]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug();
[...]
app.Run(async (context) =>
{
log.LogInformation("logging!");
await context.Response.WriteAsync("Hello World!");
});
}
}
}
also had to add an appsettings.json file to the root of the project
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

Resources