How to configure the claim name of role in IdentityServer4/Identity - c#

My problem:
[Authorize(Roles = "L1")] and User.IsInRole("L1") are looking for the claim name "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" instead of "role"
In concrete:
I have created an IdentityServer4 with the standard identity database by the following steps (http://docs.identityserver.io/en/release/quickstarts/6_aspnet_identity.html):
Create new Project
ASP.NET Core Web Application(.NET Core)
ASP.NET Core 1.1
Web Application
Change Authentication
Individual User Accounts
Add IdentityServer4.AspNetIdentity + Config.cs + ...
Then I have created a MVC-Client. The Authentication works fine. I get also a list of claims.
I am using the tables AspNetUsers, AspNetUserRoles and AspNetRoles in for the IdentityServer4 to configure role. The roles are added to the claims with the claim name "role".
If I try to authorize my controller actions in the MVC-Client it seems that the claim name for my roles is wrong.
How can I solve the conflict? Do I have to map "role" to "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"?
Here is my controller in the MVC-Client:
[Route("api/[controller]")]
public class CompaniesController : Controller
{
[HttpGet]
//[Authorize(Roles = "L1")] // This looks for the claim http://schemas.microsoft.com/ws/2008/06/identity/claims/role instead of role
public async Task<IEnumerable<Company>> GetCompaniesAsync()
{
var c = User.Identities.Count(); // 1
var nameOfExptectedRoleClaimType = User.Identities.First().RoleClaimType; // http://schemas.microsoft.com/ws/2008/06/identity/claims/role
var b0 = User.HasClaim(nameOfExptectedRoleClaimType, "L1"); // false
var b1 = User.HasClaim("role", "L1"); // true
var b2 = User.IsInRole("L1"); // false; looks for claim http://schemas.microsoft.com/ws/2008/06/identity/claims/role; used by [Authorize(Roles = "L1")]
var companies = await _crmApi.GetCompaniesAsync();
return companies;
}
}
I have found this answer (https://stackoverflow.com/a/34226538/272357) but I do not know how to "register" CustomPrinciple.

I have found an answer for myself.
I have to mention that I am using
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
to avoid the renaming of the claim names (see the code).
The resulting problem was that ClaimsPrinciple still looks for roles named with "http://schemas.microsoft.com/ws/2008/06/identity/claims/role".
This could be fixed indirectly with OpenIdConnectOptions.TokenValidationParameters.
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// ...
// Avoid claim mapping to old ms soap namespaces. Avoid replace "role" by "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
// ...
// https://leastprivilege.com/2016/08/21/why-does-my-authorize-attribute-not-work/
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role", // The role claim type is named "role" instead of "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
}
});
}
}

Related

Failing to perform Cookie Authentication: SignInAsync and AuthenticateAsync not successful

I am trying to build a very simple playground server for me to study some ASP.NET Core authentication/authorization concepts. Basically a web app with a single, very simple controller, to be tested with Postman.
I came up with a minified version of my code, consisting of a single login endpoint which would authenticate the user (no credentials required) using Cookie Authentication, like that:
[ApiController]
public class MyController : ControllerBase
{
[HttpGet("/login")]
public async Task<IActionResult> Login()
{
var claims = new[] { new Claim("name", "bob") };
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(principal);
return Ok();
}
}
The thing is that the call to HttpContext.SignInAsync() is firing the following exception:
System.InvalidOperationException: SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.
at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
at MyController.Login() in C:\Users\vinic\Desktop\TEMP\TestesAuthorization\Controllers\MyController.cs:line 18
Then I tried to replace HttpContext.SignInAsync() by a call to HttpContext.AuthenticateAsync(), so that I could authenticate the user before trying to call SignInAsync() again:
[HttpGet("/login")]
public async Task<IActionResult> Login()
{
var authResult = await HttpContext.AuthenticateAsync();
if (authResult.Succeeded == false)
return StatusCode(500, "Failed to autenticate!");
return Ok();
}
But in that case the AuthenticateAsync() result always returns a failure (authResult.Succeeded = false), and later calls to HttpContext.SignInAsync() would fail with the same InvalidOperationException as before. By enabling "Trace"-level logging, the call to AuthenticateAsync() only logs the following (not very helpful) piece of information:
dbug: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[9]
AuthenticationScheme: Cookies was not authenticated.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler: Debug: AuthenticationScheme: Cookies was not authenticated.
My project targets the net5.0 framework, has no external/explicit dependencies, and here's the Startup class I'm using:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IConfiguration configs)
{
app.UseRouting();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
I know I must be missing something really basic here. I'm also not sure if the documentation I am basing myself on is actually up-to-date for .NET 5.0.
Why is the cookie authentication (HttpContext.SignInAsync() / HttpContext.AuthenticateAsync()) failing?
This was a breaking change since Asp.Net Core 3.0 Preview 6. The documentation is here https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnetcore#identity-signinasync-throws-exception-for-unauthenticated-identity, but it does not contain the motivation of the breaking change.
The real motivation is here:
https://github.com/dotnet/aspnetcore/issues/9255
In short, you need to specify auth scheme explicitly:
new ClaimsIdentity(claims, /*Explicit*/CookieAuthenticationDefaults.AuthenticationScheme)
I had the same issue, and this change helped in my case.
Just to build on Dmitriy's answer, here is a snippet of a working login (.NET 5.0, probably works for 3.0 and above):
var claims = new List<Claim>
{
// example claims from external API
new Claim("sub", userId),
new Claim(ClaimTypes.Name, username)
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var signIn = HttpContext.SignInAsync(new ClaimsPrincipal(claimsIdentity),
_userService.AuthenticationOptions(model.RememberMe));

Blazor - Identity Add roles on application startup

I started an application in Blazor .net 3.1, and I'm having a problem. I will want to add a user with an admin role (root) when starting the application. I am using EF. Adding the user works, but adding roles throws me an exception.
System.AggregateException : 'No service for type 'Microsoft.AspNetCore.Identity.RoleManager'1[Microsoft.AspNEtCore.Identity.IdentityRole]' has been registered.ontainer is destroyed)'
I have tried different solutions, like ASP.NET Core Identity Add custom user roles on application startup, old post but I still have the same exception, on SQLite, SQL Server,...
I created a static class and in the Startup.cs I call this method.
public static class RolesData
{
private static readonly string[] Roles = new string[] { "Admin", "Manager", "Member" };
public static async Task SeedRoles(IServiceProvider serviceProvider)
{
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
foreach (var role in Roles)
{
if (!await roleManager.RoleExistsAsync(role))
{
await roleManager.CreateAsync(new IdentityRole(role));
}
}
}
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
RolesData.SeedRoles(app.ApplicationServices).Wait();
}
If you have any suggestions I'm interested, and also if you know of a site that explains authentication with Identity, I want to understand!
Thank you for your help
By the error it appears you have not configured the Identity server to expose Roles.
For example in Startup.cs
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>() // <------
.AddEntityFrameworkStores<ApplicationDbContext>();
I have a standard template with roles seeded here
It goes further to show how to enable the use of the Authorize attribute:
#attribute [Authorize(Roles = "Administrator")]
and AuthorizeView :
<AuthorizeView Roles="Administrator">
Only Administrators can see this.<br />
</AuthorizeView>
<AuthorizeView Roles="Moderator">
Only Moderators can see this.<br />
</AuthorizeView>
<AuthorizeView Roles="Moderator,Administrator">
Administrators and Moderators can see this.
</AuthorizeView>
The changes I made to a standard project to enable Roles and make them visible to Blazor WebAssembly can be seen in this commit
Microsoft has a good guide about this:
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-6.0&tabs=visual-studio#name-and-role-claim-with-api-authorization
In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single role claim. A single role is sent as a string value in the claim. The factory creates an individual role claim for each of the user's roles.
CustomUserFactory.cs:
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
public class CustomUserFactory
: AccountClaimsPrincipalFactory<RemoteUserAccount>
{
public CustomUserFactory(IAccessTokenProviderAccessor accessor)
: base(accessor)
{
}
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
RemoteUserAccount account,
RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
if (user.Identity.IsAuthenticated)
{
var identity = (ClaimsIdentity)user.Identity;
var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();
if (roleClaims.Any())
{
foreach (var existingClaim in roleClaims)
{
identity.RemoveClaim(existingClaim);
}
var rolesElem = account.AdditionalProperties[identity.RoleClaimType];
if (rolesElem is JsonElement roles)
{
if (roles.ValueKind == JsonValueKind.Array)
{
foreach (var role in roles.EnumerateArray())
{
identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
}
}
else
{
identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
}
}
}
}
return user;
}
}
In the Client app, register the factory in Program.cs:
builder.Services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<CustomUserFactory>();
In the Server app, call AddRoles on the Identity builder, which adds role-related services:
using Microsoft.AspNetCore.Identity;
...
services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
API authorization options
In the Server app:
Configure Identity Server to put the name and role claims into the ID
token and access token.
Prevent the default mapping for roles in the JWT token handler.
Startup.cs (Program.cs in .NET6):
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
...
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
Microsofts guide says Use one of the following approaches: API authorization options or Profile Service but using a Profile Service like public class ProfileService : IProfileServic only works with Authorization Code Grant and not Resource Owner Password Credentials.
See here for more info:
https://stackoverflow.com/a/74058054/3850405
In Program.cs for ASP.NET Core 6.0 or later:
using System.Security.Claims;
...
builder.Services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
In Startup.ConfigureServices for versions of ASP.NET Core earlier than 6.0:
using System.Security.Claims;
...
services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
See my answer on seeding all kinds of data in ASP.NET Core (works in 3.1) using the IEntityTypeConfiguration
I have not tried it on blazor yet but i think it might worth the try.
Note: Changes requires a db migration.

Role based authorization in ASP.NET Core 3.1 with Identity and ExternalLogin

Im new to .NET Core and I'm trying to setup Role based authorization in a .NET Core 3.1 project. I believe I clicked on every tutorials and threads talking about it online. My problem is that it seems to be working very easily on the tutorials, but it doesn't work for me. According to tutorials I have found, all I would have to do is assign a role to a user in a database, then use [Authorize(Roles="roleName")] before a Controller's Action. When I do that I always get a 403 error for a user having the specified role. When I use userManager.GetRolesAsync(user), I see that the user has the role. When I make a request to this action with [Authorize], it works when the user is logged in, as expected.
I checked in debug mode ClaimsPrincipal.Identity for the current user and I found out that RoleClaimType = "role". I checked the claims of the current user and found out that it doesn't have a claim with a type "role". Is this how [Authorize(Roles="...")] works? Does it look a the claims? If so, how do I had a claim for the user's role? The only way for a user to login in this application is with a Google account. So how am I supposed to add a claim if they are managed by the Google login?
Here's my code in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddGoogle(options =>
{
IConfigurationSection googleAuthNSection =
Configuration.GetSection("Authentication:Google");
options.ClientId = googleAuthNSection["ClientId"];
options.ClientSecret = googleAuthNSection["ClientSecret"];
})
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
Here's an exemple of an Action of a Controller
[Authorize(Roles = "Admin")]
[HttpGet("userinformations")]
public async Task<UserInformations> GetCurrentUserInformations()
{
string strUserId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
ApplicationUser user = await userManager.FindByIdAsync(strUserId);
string[] roles = (await userManager.GetRolesAsync(user)).ToArray();
UserInformations userInfo = new UserInformations()
{
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
Organization = user.idDefaultOrganisation.HasValue ? user.DefaultOrganization.OrganizationName : "",
Claims = this.User.Claims.Select(c => $"{c.Type} : {c.Value}").ToArray(),
Roles = roles
};
return userInfo;
}
When I make a request to this Action without [Authorize(Roles = "Admin")], I can see that the current user has the role Admin, but when I add it, I get a 403 error.
What am I doing wrong? I feel like I'm missing one line somewhere or something like that because it all seems so simple in the tutorials I found.
Your assumption was correct, when you specify the [Authorize(Roles = "<role>")] attribute, ASP will create a RolesAuthorizationRequirement behind the scene.
Then the authorization handler will call this.HttpContext.User.IsInRole(<role>) to evaluate the policy.
In your case, the call is this.HttpContext.User.IsInRole("Admin")
The method User.IsInRole will look into a claim named "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" and compare its value to "Admin"
The ASP Authorization pipeline is not hooked to your UserManager logic, the basic API will only observe and validate the JWT token claims.
You should probably create your own AuthorizationHandler that checks if the user is indeed Admin
Or the less formal way using RequireAssertion :
services.AddAuthorization(options => options.AddPolicy("Admininstrators", builder =>
{
builder.RequireAssertion(async context =>
{
string strUserId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await userManager.FindByIdAsync(strUserId);
string[] roles = (await userManager.GetRolesAsync(user)).ToArray();
return roles.Contains("Admin");
};
});
[Authorize("Admininstrators")]
[HttpGet("userinformations")]
public async Task<UserInformations> GetCurrentUserInformations()
{
...
}
I finally found a working solution.
I tried adapting #MichaelShterenberg 's code using RequireAssertion, but I couldn't get it to work because I had to query my database and I was not able to use UserManager with this solution.
I ended up finding a solution based on this part of his answer :
You should probably create your own AuthorizationHandler that checks if the user is indeed Admin
I followed the answer of this thread : Dependency Injection on AuthorizationOptions Requirement in DotNet Core

How to pass created user claims to client

I have created multiple claims that sit in the AspNetUserClaims table for identity and have assigned them to my user id.
I am currently trying to get these to pull through in the list of claims I receive in my client application.
I have managed to pull through all the roles from the AspNetUserRoles table by adding the 'roles' scope to my client identity settings and then also in identity configuration (using the EF database format a.k.a ConfigurationDbContext) created a record in the IdentityResources table which links to an identity claim called 'role'.
This is working as expected. However, I am not getting any of my UserClaims I have created through, do I need to create another specific scope?
Here is my client configuration:
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookie")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:44335/";
options.ClientId = "openIdConnectClient";
options.SignInScheme = "cookie";
options.ResponseType = "id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid profile roles all_claims");
});
services.AddAuthorization();
this is how I'm checking what claims the user has:
var claims = ((ClaimsIdentity)User.Identity).Claims;
and it returns all roles and profile claims (e.g. preferred_username) just not those specified within the AspNetUserClaims table.
For my client I have also set the property [AlwaysIncludeUserClaimsInIdToken] to true with no luck.
Does anyone know what I'm missing to pass through the user claims?
Do you have a IProfileService implementation to populate your custom claims?
You should implemet IProfileService as indicated in this answer.
Try other response_type than id_token since your application does not have an access token to call User Info endpoint. Maybe with id_token token to maintain the implicit flow grant of your client.
you can get the user claims like this:
var claims = User.Claims.Select(c => new { c.Type, c.Value });
you can implement this as an endpoint in your api which you stated as scope in your identity server:
using IdentityServer4;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace IdentityServer4Demo.Api
{
[Route("/api/test")]
[Authorize]
public class TestController : ControllerBase
{
public IActionResult Get()
{
var claims = User.Claims.Select(c => new { c.Type, c.Value });
return new JsonResult(claims);
}
}
}
If you want to add more claim you need to add property to the class which implements IdentityUser and use it in your custom profile service
using Microsoft.AspNetCore.Identity;
namespace AuthServer.Infrastructure.Data.Identity
{
public class AppUser : IdentityUser
{
// Add additional profile data for application users by adding properties to this class
public string Name { get; set; }
}
}
your custom profile service:
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AuthServer.Infrastructure.Constants;
using AuthServer.Infrastructure.Data.Identity;
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Identity;
namespace AuthServer.Infrastructure.Services
{
public class IdentityClaimsProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<AppUser> _claimsFactory;
private readonly UserManager<AppUser> _userManager;
public IdentityClaimsProfileService(UserManager<AppUser> userManager, IUserClaimsPrincipalFactory<AppUser> claimsFactory)
{
_userManager = userManager;
_claimsFactory = claimsFactory;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
claims.Add(new Claim(JwtClaimTypes.GivenName, user.Name));
claims.Add(new Claim(IdentityServerConstants.StandardScopes.Email, user.Email));
// note: to dynamically add roles (ie. for users other than consumers - simply look them up by sub id
claims.Add(new Claim(ClaimTypes.Role, Roles.Consumer)); // need this for role-based authorization - https://stackoverflow.com/questions/40844310/role-based-authorization-with-identityserver4
context.IssuedClaims = claims;
}
}

InvalidOperationException: The AuthorizationPolicy named: 'Bearer' was not found

I am currently trying to learn how to build a secure api using bearer token, I keep getting this error (InvalidOperationException: The AuthorizationPolicy named: 'Bearer' was not found.) and I am not sure why. I am using asp.net-core 2.0 and trying to use the jwt auth middleware.
Here is my startup class, any help would be greatly appreciated!
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
const string TokenAudience = "ExampleAudience";
const string TokenIssuer = "ExampleIssuer";
private RsaSecurityKey key;
private TokenAuthOptions tokenOptions;
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)
{
var keyParams = RSAKeyUtils.GetRandomKey();
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
Audience = TokenAudience,
Issuer = TokenIssuer,
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature)
};
services.AddDbContext<VulnerabilityContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<LoggingActionFilter>();
services.AddScoped<VulnsService>();
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = "https://localhost:54302";
o.Audience = tokenOptions.Audience;
o.RequireHttpsMetadata = false;
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//app.UseSession();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
You get this error because authentication schemes and authorization policies are not the same thing. Let's see what each of them are.
Authentication schemes
They are the different methods of authentication in your application. In the code you posted, you have one authentication scheme which is identified by the name Bearer and the options you specified.
It is possible to have several authentications schemes set up in one single application:
You could authenticate users with cookies or JWT bearer tokens authentication
You could even accept JWT tokens from different sources; in this case, you would need to call the AddJwtBearer method twice. It is also important to note that the name of the authentication scheme is supposed to be unique, so you'd need to use the overload that takes the name and the options configuration delegate
Authorization policies
When a user is authenticated in your application, it doesn't mean it can access every single feature in it. You might have different access levels where administrators have special rights that no one else does; this is expressed in ASP.NET Core using authorization policies. I highly suggest that you read the official documentation on authorization as I think it's great.
An authorization policy is made of two things:
a unique name
a set of requirements
Taking the example of administrators mentioned above, we can create a fictional authorization policy:
Name: Administrators
Requirements: Must be authenticated and have a role claim with the Administrators value
This would be expressed this way in code:
services.AddAuthorization(options =>
{
options.AddPolicy("Administrators", new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("role", "Administrators")
.Build());
});
You could then apply this policy on some specific controllers or actions in your application by decorating them with an [Authorize(Policy = "Administrators")] attribute. MVC would then, during the request, run the requirements against the current user and determine whether they can access the specific feature.
My guess is that you added such an attribute on one of your actions/controllers, but you didn't register an authorization policy names Bearer in the authorization system.
If your goal is to prevent non-authenticated users to access some actions, you could apply an [Authorize] attribute. Doing so would run the default policy which, by default, only requires the user to be authenticated.
I'm not working with policies and this error happened to me when I forgot to indicate the roles in the authorize attribute.
I had this:
[Authorize("Administrator")] // if you don't specify the property name Roles it will consider it as the policy name
Fixed it by changing it to:
[Authorize(Roles = "Administrator")]
Adding the AuthenticationSchemes to the controller class works for me:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Categories

Resources