Asp.net Web API .NET Core 3.1 and Azure AD - system.unauthorizedaccessexception: neither scope or roles claim was found in the bearer token - asp.net-core

I am trying to secure my Web Api with Azure AD. This application will be accessed by a console app, and the token will be generated from a client id / secret. I followed the quickstart from https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-web-api .
After obtaining a client token and sending through the bearer auth header, I am getting an error
System.UnauthorizedAccessException: IDW10201: Neither scope or roles claim was found in the bearer token.
I'm obtaining an access token with this code:
public static async Task<string> GetAccessToken(string aadInstance, string aadTenant, string aadClientId, string aadClientSecret, string apiResourceId)
{
string authority = aadInstance.TrimEnd('/') + "/" + aadTenant;
var app = ConfidentialClientApplicationBuilder.Create(apiResourceId)
.WithClientId(aadClientId)
.WithClientSecret(aadClientSecret)
.WithAuthority(authority)
.Build();
var tokenrequest = app.AcquireTokenForClient(new string[] { "api://resourceid/.default" });
var tokenresult = await tokenrequest.ExecuteAsync();
return tokenresult.AccessToken;
}
My startup code in the web api looks like this:
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration);
later in the startup...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseOpenApi();
app.UseSwaggerUi3();
}

It turns out that the setup in Azure AD was missing a role that needed to be added to the manifest as well as the permissions to the api of the client application, as per step 8 in https://dotnetplaybook.com/secure-a-net-core-api-using-bearer-authentication/
Unfortunately the MS documentation doesn't put this part in the quick start.

Related

How do I authorise the Hangfire Dashboard via Microsoft Single Sign-On with Angular 12 and ASP.Net Core 5

My application is an Angular 12 application running on ASP.Net Core 5.
I am currently trying to lock down Hangfire so that it will only work for people with the Admin role.
It uses Microsoft Identity to log in - specifically Single Sign-on, set up in Azure.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHangfire(x =>
{
x.UseSqlServerStorage(sqlServerConnectionString);
});
...
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration);
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] {
new HangfireAuthorisationFilter()
},
AppPath = "/"
});
...
app.UseEndpoints(endpoints => {
...
});
app.UseSpa(spa=>{
...
});
}
This works in my dot net core controllers.
All I need to do to get it to work is add the Authorize attribute:
namespace MyAppName.Controllers
{
[Produces("application/json")]
[Route("api/MyRoute")]
[Authorize(Roles="Role1,Role2,Administrator")]
public class MyControllerController: MyBaseApiController
{
...
}
}
But when I want to Authorise in Hangfire, the User object is missing a whole lot of its properties.
Here is the HangfireAuthorisationFilter:
public class HangfireAuthorisationFilter : IDashboardAuthorizationFilter
{
public HangfireAuthorisationFilter()
{
}
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
// the next line always fails. The User object is set. The Identity object is set
// but there are no claims and the User.Name is null. There are also no roles set.
return httpContext.User.Identity.IsAuthenticated;
}
}
There is, however, cookie information, containing the msal cookie:
How can I pass authentication information into the Hangfire Authorize method? How can I access the role information so that I can lock it down to just the Admin role? Is there a way I can decode the msal cookie server-side?
Assuming you have an AzureAd configuration block that looks like below:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
"TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
"ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]"
}
I think a better approach to avoid manual validation of the token is to change your code to the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(x =>
{
x.UseSqlServerStorage(sqlServerConnectionString);
});
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration);
services.
.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddAuthorization(options =>
{
options.AddPolicy("Hangfire", builder =>
{
builder
.AddAuthenticationSchemes(AzureADDefaults.AuthenticationScheme)
.RequireRole("Admin")
.RequireAuthenticatedUser();
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHangfireDashboard("/hangfire", new DashboardOptions()
{
Authorization = Enumerable.Empty<IDashboardAuthorizationFilter>()
})
.RequireAuthorization("Hangfire");
});
}
To break this down, the following changes have been made:
Add authentication for AzureADDefaults.AuthenticationScheme so we can create a policy requiring the "Admin" role.
Add a policy named "Hangfire" that requires the "Admin" role against a user. See the AddAuthorization call.
Instead of calling UseHangfireDashboard we call MapHangfireDashboard inside UseEndpoints and protect the hangfire dashboard endpoint using our "Hangfire" policy through the call to RequireAuthorization("Hangfire")
Removal off the HangfireAuthorisationFilter which is not needed and instead we pass an empty collection of filters in the MapHangfireDashboard call.
The key takeaway is that we are now relying on the security provided by the middleware rather than the implementation of IDashboardAuthorizationFilter which comes with huge risk around the token being invalid and/or a mistake is made in the logic.
Ok I have figured out how to decode the msal cookie to get my list of claims and roles, and authorise successfully with Hangfire
using Hangfire.Dashboard;
using System.IdentityModel.Tokens.Jwt;
namespace MyApp.Filters
{
public class HangfireAuthorisationFilter : IDashboardAuthorizationFilter
{
public HangfireAuthorisationFilter()
{
}
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var cookies = httpContext.Request.Cookies;
var msalIdToken = cookies["msal.{your app client id goes here}.idtoken"];
var token = new JwtSecurityTokenHandler().ReadJwtToken(msalIdToken);
foreach(var claim in token.Claims)
{
if (claim.Type=="roles" && claim.Value == "Admin")
{
return true;
}
}
return false;
}
}
}

how to auto-start/warm up .net core web app hosted in IIS

I have a .net core (3.1) web app which is hosted on IIS. I cannot figure out how to run a piece of code before the first request. I have done the following:
set the 'start mode' of the app pool = "AlwaysRunning" and "Idle time-out" = 0
set the 'preload enabled' = true on the web site
What i am missing is where/how i register the code/service that i would like to run before the first request comes in?
thanks in advance
What i am missing is where/how i register the code/service that i would like to run before the first request comes in?
If you want to call one of your mvc or web api after the application has start up completely to warm up your web application. You could try to use IHostApplicationLifetime's ApplicationStarted method.
This method will be called after the application started immediately.
You could inject IHostApplicationLifetime into Configure() method , then write the callback for ApplicationStarted that would be triggered when the application host has fully started.
More details, you could refer to below example:
Register httpclient service in Startup.cs ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddControllersWithViews();
}
Add lifetime.ApplicationStarted.Register callback in Configure method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Default}/{action=Index}/{id?}");
});
IHttpClientFactory httpClientFactory = app.ApplicationServices.GetService(typeof(IHttpClientFactory)) as IHttpClientFactory;
lifetime.ApplicationStarted.Register(onApplicationStartedAsync(httpClientFactory).Wait);
}
private async Task<Action> onApplicationStartedAsync(IHttpClientFactory httpClientFactory)
{
var httpclient = httpClientFactory.CreateClient();
var httpMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/api/values");
var httpresponse = await httpclient.SendAsync(httpMessage);
if (httpresponse.IsSuccessStatusCode)
{
string res = await httpresponse.Content.ReadAsStringAsync();
}
return null;
}
Result:

How does Authentication works in asp.net core3.0?

I have the following code in Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
services.
AddAuthentication("CookieAuth").
AddCookie("CookieAuth", config =>
{
config.Cookie.Name = "user.cookie";
config.LoginPath = "/Home/Login";
});
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();//determining a user's identity
app.UseAuthorization();//determines what a user is able to do
app.UseEndpoints(opt => {
opt.MapDefaultControllerRoute();
});
}
The above configuration works fine, it doesn't have any exception. But I can't figure out how the Authentication works. I have known the following concepts:
Authentication determines the user's identity.
Authorization determines what a user is able to do.
I didn't know the execution sequential. I mean:
does the Authentication always check the user's identity per request? Even though these requests are in the same session?
What's will happen if a request doesn't have a valid user's identity(e.g: on the first request)? is it going to continue executing the Authorization?

Redirect to HTTPS in Blazor

I have a blazor app.
I hosted it on server and have access with https.
But when i do redirect (in one controller), happens exception.
Startap.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseMvc(routes =>
{
routes.MapRoute(name: "default", template: "{controller}/{action}/{id?}");
});
app.Map("/schedule", subdirApp =>
{
subdirApp.UseBlazor<Client.Startup>();
});
}
And method in controller
[HttpGet]
[Route("***")]
public IActionResult Return()
{
FileStream fs = new FileStream(_filePath, FileMode.Open);
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
List<ScheduleEntity> _list = (List<ScheduleEntity>)formatter.Deserialize(fs);
foreach (var x in _list)
Schedules.Add(x);
fs.Close();
return Redirect("~//schedule");
}
Exception
Please, help me
These API responses can be a bit misleading. Without seeing the rest of your code around the configuration of endpoints, I suspect this might be a CORS issue with the API.
Try adding the following code to the public void Configure(IApplicationBuilder app, IHostingEnvironment env) method in your API's Startup.cs class:
app.UseCors(opts => opts
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
The fetch response may be due to the request preflight being rejected.
Having said that, the first exception message is saying you're trying to load insecure content, so I'd also check your Blazor front-end app's configuration to see what the API client is requesting and ensure the API endpoint certificate is valid?

ASP .Net Core Google Authentication

I have a problem with google authentication on my .net core web api application.
My use case is simple, get bearer token from google put token in authorization header as "Bearer {token}" and call my web api.
But I cannot make it work. After I get token from google on following url:
https://accounts.google.com/o/oauth2/v2/auth?scope=email%20openid&include_granted_scopes=true&state=some_test_state&redirect_uri=http%3A%2F%2Flocalhost%3A53512&response_type=token&client_id={someClientID}
I will make call to my api with header:
Authorization: Bearer {TokenValue}
But every time I'm getting 401 Unauthorized.
This is my Startup class:
public static IConfigurationRoot Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Pull in any SDK configuration from Configuration object
services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
// Add S3 to the ASP.NET Core dependency injection framework.
services.AddAWSService<Amazon.S3.IAmazonS3>();
IocConfig.Configure(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddLambdaLogger(Configuration.GetLambdaLoggerOptions());
var googleOptions = new GoogleOptions
{
AuthenticationScheme = "Google",
ClientId = "clientid",
ClientSecret = "cs",
SignInScheme = "Google"
};
app.UseGoogleAuthentication(googleOptions);
app.UseDeveloperExceptionPage();
app.UseMvc();
}
It's because your authentication scheme is "Google", but if you want to use bearer token you need to add it to your startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// here's your options
})
And use this authentication scheme instead of "Google"