I created an API (.Net Core 2 with EF Core) with an endpoint to retrieve certain roles. I integrated ASPNetIdentity into my project and I am using AspNetRoles and the AspNetRoleClaims.
When calling the API the user has a specific role (Admin) in my case. This role has a few role claims. In the startup.cs I added policies for this role:
options.AddPolicy(
"Create Roles", policy => policy.RequireClaim("Can create roles", "role.create"));
options.AddPolicy(
"View Roles", policy => policy.RequireClaim("Can read roles", "role.read"));
options.AddPolicy(
"Edit Roles", policy => policy.RequireClaim("Can update roles", "role.update"));
options.AddPolicy(
"Delete Roles", policy => policy.RequireClaim("Can delete roles", "role.delete"));
In my Frontend the users can login with their Microsoft (azure) account and their oidc claim (ID) matches the ID in the AspNetUser table, if their oidc is not found in the user table, they are automatically added (with their oidc id as the aspnetuser id) and they get a default role.
When calling the Role endpoint however, it always returns a 403 error (forbidden). When I check the tables, the user has the right role and role claims to access the endpoint. How is it possible that it keeps returning 403?
The endpoint looks as follow:
[HttpGet]
[Authorize(Policy = "View Roles")]
public IEnumerable<IdentityRole> GetRole()
{
return _context.Roles;
}
After some research I found a post which tells that you need to include the role (claim) of the user in the token which is being send to the API, though this would mean I will need a GET endpoint that first returns the role(s) of the user, the frontend needs to pick it up and add it to the token, and then call all the other endpoints with the role included in the token? Or am I on the wrong track here?
----UPDATE----
I am 90% sure that the Policy/Authorization check needs the Role claim to be included in Token of the user. The process however is as following right now:
Now that the user is also added in the AspNetUser table, I can use the Asp.Net Identity for doing Authorization using Roles and RoleClaims.
The problem however is that the initial received token by the API is the Azure token, which knows nothing about my Identity tables (correct me if I am wrong). I believe this is also the cause of my policy not working (again, correct me if I am wrong).
I found a post where the problem is more or less the same (https://joonasw.net/view/adding-custom-claims-aspnet-core-2), the trick is to extend the current token with my needed Identity claims such as ClaimTypes.Role
.
My code to achieve this is as follow:
// Add authentication (Azure AD)
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; // Use JWT as ChallendgeSchema, if not, ASPNet Identity will be used by default and this will return a default non-existing endpoint (because it is not created): Account/Login; https://stackoverflow.com/questions/45878166/asp-net-core-2-0-disable-automatic-challenge
})
.AddJwtBearer(options =>
{
options.Audience = this.Configuration["AzureAd:ClientId"];
options.Authority = $"{this.Configuration["AzureAd:Instance"]}{this.Configuration["AzureAd:TenantId"]}";
// Added events which checks if the user (token user) exists in our own database, if not then the user is being added with a 'User' role
options.Events = new JwtBearerEvents()
{
OnTokenValidated = context =>
{
// Check if roles are present
CheckRoles cr = new CheckRoles();
cr.CreateRoles(services.BuildServiceProvider());
// Check if the user has an OID claim(oid = object id = user id)
if (!context.Principal.HasClaim(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier"))
{
context.Fail($"The claim 'oid' is not present in the token.");
}
ClaimsPrincipal userPrincipal = context.Principal;
CheckUser cu = new CheckUser();
cu.CreateUser(userPrincipal, services.BuildServiceProvider());
// Extend the current token with my (test) Role claim
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "Admin")
};
var appIdentity = new ClaimsIdentity(claims);
context.Principal.AddIdentity(appIdentity);
return Task.CompletedTask;
}
};
});
Sadly the above does not work, when calling the API from the frontend, the token remains unchanged and the RoleClaim is not being added.. Anyone has a clue how to add my RoleClaim to the token so that I can use my policies?
When calling the API the user has a specific role (Admin) in my case. This role has a few role claims.
If a user has role.read
as ClaimTypes.Role
in principal object, them you can create policy like the following in Startup.cs
-
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.AddPolicy("View Roles", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser()
.RequireAssertion(context =>
context.User.HasClaim(ClaimTypes.Role, "role.read"))
.Build();
});
});
...
}
You need to add JwtBearerDefaults.AuthenticationScheme
authentication type to claims identity, so that it matches with default scheme.
services
.AddAuthentication(sharedOptions =>
{
...
})
.AddJwtBearer(options =>
{
...
options.Events = new JwtBearerEvents()
{
OnTokenValidated = context =>
{
...
var appIdentity = new ClaimsIdentity(claims,
JwtBearerDefaults.AuthenticationScheme);
^^^^^
context.Principal.AddIdentity(appIdentity);
return Task.CompletedTask;
}
};
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With