Blazor WASM application ClaimsPrinicpal and roles claim - c#

I am trying to build a Blazor WASM application that can display different UI elements depending on the value(s) in the roles claim of the token received from Azure AD.
I have a simple debug view where I iterate all claims:
#foreach(var claim in #context.User.Claims)
{
#claim
<br/>
}
Here I can clearly see the user has the following claim:
roles: ["Developer"]
This is received from the AAD app registration where I have assigned the role Developer to my own user.
I would expect any of these statements to then return true:
context.User.HasClaim(ClaimTypes.Role, "Developer") // false
context.User.IsInRole("Developer") // false
So I wrote a custom implementation and parse the claim myself:
#if(context.User.Identity?.IsAuthenticated)
{
var rolesValue = context.User.Claims.Where(c => c.Type == "roles").First().Value; // The roles claim value is an array as a string
// Deserialize the string into a list
var roles = JsonSerializer.Deserialize<List<string>>(rolesValue);
// Print out all roles
Console.WriteLine("Roles:");
foreach(var role in roles!)
{
Console.WriteLine(role); // Prints 'Developer'
}
}
All of these snippets are run inside the following Blazor components:
<AuthorizeView>
<Authorized>
</Authorized>
</AuthorizeView>
The entire application is set up using the following tutorial.
How come I have to do this custom claim interpretation when I clearly have the claim for the user? Just Googling this issue returns so many results but I still haven't been able to solve it. The documentation here uses the method in a slightly different way, but why can I enumerate the claim in my context but still not use any of the utility methods on the User?
What am I missing here?
EDIT:
Implemented the sample from docs:
#using Microsoft.AspNetCore.Authorization
#using Microsoft.AspNetCore.Components.Authorization
#inject IAuthorizationService AuthorizationService
protected async override Task OnInitializedAsync()
{
var user = (await authenticationStateTask).User;
if (user.IsInRole("Developer"))
{
Console.WriteLine("Developer role from OnInitialized");
} else
{
Console.WriteLine("No role from OnInitialized"); // Always gets here, even after logging/in out or using private browsing
}
}
The enumerated claims still list the developer role in the roles claim: roles: ["Developer"].

Seems like there is a ton of ways to solve this depending on your auth scenario. For my scenario, I needed to know the role when authenticating in a SPA against Azure AD. The following instructions solved that.
I only made one modification to those instructions, and that was to the CustomAccountFactory. I set the role claim that enabled me to use to built in User.IsInRole
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
CustomUserAccount account,
RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);
if (initialUser.Identity.IsAuthenticated)
{
var userIdentity = (ClaimsIdentity)initialUser.Identity;
foreach (var role in account.Roles)
{
userIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
return initialUser;
}
The problem with using User.IsInRole initially seems like I was receiving an array of roles as a string.
This could also work, but would be fragile:
User.IsInrole(#"[""Developer""]")

Related

Accessing user information via `IHttpContextAccessor` from project created with `dotnet new react -au Individual`?

Background
I've been following the documentation for using IdentityServer4 with single-page-applications on ASP.NET-Core 3.1 and as such created a project via the dotnet new react -au Individual command.
This creates a project which uses the Microsoft.AspNetCore.ApiAuthorization.IdentityServer NuGet package.
So far it's been really great and it got token-based authentication for my ReactJS application working without any pain!
From my ReactJS application, I can access the user information populated by the oidc-client npm package such as the username.
Also, calls to my Web APIs with the [Authorize] attribute work as expected: only calls with a valid JWT access token in the request header have access to the API.
Problem
I'm now trying to access basic user information (specifically username) from within a GraphQL mutation resolver via an injected IHttpContextAccessor but the only user information I can find are the following claims under IHttpContextAccessor.HttpContext.User:
nbf: 1600012246
exp: 1600015846
iss: https://localhost:44348
aud: MySite.HostAPI
client_id: MySite
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: (actual user GUID here)
auth_time: 1600012235
http://schemas.microsoft.com/identity/claims/identityprovider: local
scope: openid
scope: profile
scope: MySite.HostAPI
http://schemas.microsoft.com/claims/authnmethodsreferences: pwd
The same issue happens for Web API controllers as well.
Details
MySite is the namespace of my solution and is also what I have defined as a client in my appsettings.json file:
{
"IdentityServer": {
"Clients": {
"MySite": {
"Profile": "IdentityServerSPA"
}
}
}
}
My web application project's name is MySite.Host so MySite.HostAPI the name of the resource and scope that are automatically generated by calling AuthenticationBuilder.AddIdentityServerJwt().
... this method registers an <<ApplicationName>>API API resource with IdentityServer with a default scope of <<ApplicationName>>API and configures the JWT Bearer token middleware to validate tokens issued by IdentityServer for the app.
Research
According to a few answers on Stack Overflow, adding IdentityResources.Profile() resource via IIdentityServerBuilder.AddInMemoryIdentityResources() should do the trick but it looks like it's already available via the claims I posted above (scope: profile).
I nevertheless tried it but the result is that the authentication flow becomes broken: the redirect to the login page does not work.
All of the answers I've found also make a reference to a Config class like in this demo file which holds configurations that are mainly fed to IIdentityServerBuild.AddInMemory...() methods.
However, it seems that Microsoft.AspNetCore.ApiAuthorization.IdentityServer does most of this in its implementation and instead offers extendable builders to use.
From the IdentityServer documentation, I don't believe I need to add a Client because the access token already exists. The client ReactJS application uses the access_token from oidc-client to make authorised calls to my Web APIs.
It also doesn't appear like I need to add a Resource or Scope for the username information because I believe these already exist and are named profile. More to this point is that the documentation for "IdentityServerSPA" client profile states that:
The set of scopes includes the openid, profile, and every scope defined for the APIs in the app.
I also looked at implementing IProfileService because according to the documentation this is where additional claims are populated. The default implementation is currently being used to populate the claims that are being requested by the ProfileDataRequestContext.RequestedClaimTypes object and this mechanism already works because this is how the ReactJS client code receives them. This means that when I'm trying to get the user claims from ASP.NET-Core Identity, it's not properly populating ProfileDataRequestContext.RequestedClaimTypes or perhaps not even calling IProfileServices.GetProfileDataAsync at all.
Question
Considering that my project uses Microsoft.AspNetCore.ApiAuthorization.IdentityServer, how can I view the username from my ASP.NET-Core C# code, preferably with IHttpContextAccessor?
What you need to do is to extend the default claims requested by IdentityServer with your custom ones. Unfortunately, since you're using the minimalistic IdentityServer implementation by Microsoft, the correct way of making the client request the claims isn't easy to find. However, assuming you have only one application (as per the template), you could say that the client always wants some custom claims.
Very important first step:
Given your custom IProfileService called, say, CustomProfileService, after these lines:
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
you have to get rid of the implementation used in the scaffolded template, and use your own:
services.RemoveAll<IProfileService>();
services.AddScoped<IProfileService, CustomProfileService>();
Next, the actual implementation of the custom IProfileService isn't really hard if you start from Microsoft's version:
public class CustomProfileService : IdentityServer4.AspNetIdentity.ProfileService<ApplicationUser>
{
public CustomProfileService(UserManager<ApplicationUser> userManager,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory) : base(userManager, claimsFactory)
{
}
public CustomProfileService(UserManager<ApplicationUser> userManager,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
ILogger<ProfileService<ApplicationUser>> logger) : base(userManager, claimsFactory, logger)
{
}
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
string sub = context.Subject?.GetSubjectId();
if (sub == null)
{
throw new Exception("No sub claim present");
}
var user = await UserManager.FindByIdAsync(sub);
if (user == null)
{
Logger?.LogWarning("No user found matching subject Id: {0}", sub);
return;
}
var claimsPrincipal = await ClaimsFactory.CreateAsync(user);
if (claimsPrincipal == null)
{
throw new Exception("ClaimsFactory failed to create a principal");
}
context.AddRequestedClaims(claimsPrincipal.Claims);
}
}
With those two steps in place, you can start tweaking CustomProfileService's GetProfileDataAsync according to your needs. Notice that ASP.NET Core Identity by default already has the email and the username (you can see these in the claimsPrincipal variable) claims, so it's a matter of "requesting" them:
// ....
// also notice that the default client in the template does not request any claim type,
// so you could just override if you want
context.RequestedClaimTypes = context.RequestedClaimTypes.Union(new[] { "email" }).ToList();
context.AddRequestedClaims(claimsPrincipal.Claims);
And if you want to add custom data, for example, the users first and last name:
// ....
context.RequestedClaimTypes = context.RequestedClaimTypes.Union(new[] { "first_name", "last_name" }).ToList();
context.AddRequestedClaims(claimsPrincipal.Claims);
context.AddRequestedClaims(new[]
{
new Claim("first_name", user.FirstName),
new Claim("last_name", user.LastName),
});
User information can be retrieved via the scoped UserManager<ApplicationUser> service which is set up by the project template. The users's claims contains "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" (ClaimTypes.NameIdentifier) whose value is the user identifier. UserManager<>.FindByIdAsync() can then be used to retrieve the ApplicationUser associated with the user and which contains additional user information.
Note that this contacts the user store each time it's invoked. A better solution would be to have the extra user information in the claims.
First, explicitly add the IHttpContextAccessor service if you haven't already by calling services.AddHttpContextAccessor();
From within an arbitrary singleton service:
public class MyService
{
public MyService(
IHttpContextAccessor httpContextAccessor,
IServiceProvider serviceProvider
)
{
var nameIdentifier = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
using (var scope = serviceProvider.CreateScope())
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var user = await userManager.FindByIdAsync(nameIdentifier);
// Can access user.UserName.
}
}
}
UserManager<ApplicationUser> can be accessed directly within Razor pages and Controllers because these are already scoped.

ASP.NET core Web API: Authorization based on permissions from database

I am looking for a solution/suggestion that helps me creating permission based access to web api endpoints/controller actions.
Role based access is not suitable becuase I don't have fixed rules that I could use in code like Role("Admin") oder Role("Controller").
Claim based permissions is also not feasable because each user/client can have different permissions on each business object/entity (e.g. Read/Write-access to own tickets and read access to all ticket of his/her company or if its a technician of my company full access to all tickets of all customers. So each user would have 10s or even hundrets of claims which I would have to evaluate at each access of my API.
It is some kind of multi tenancy in just on database and the tenants are our customers with some kind of "master tenant" that has access to all of the tenant data.
I think that something like Visual Guard would satisfy my needs but it is pretty expensive and they don't support net core for now and their documentation seems pretty outdated.
I don't need a usable solution at once but some hints and tricks how I could achieve that would very much be apprieciated because I am looking and searching for some time now.
Details on "database permissions":
What I mean is in my frontend (Winforms app) I want to establish a security system where I can create and assign roles to users and in those roles is defined which actions a user can execute and which CRUD operations he/she can do on specific business objects. Each role can have n users and each role can have n permissions. Each permission on itself declares for exmaple Create:false, Read:true, Write:true and Delete:false. If a permission for a specific business object is not found CRUDs on that BO is denied totally.
So whenever an action in my API is called I have to check if that user and his/her rule allows him to do that specific action based on rules and permissions in my database.
Details an application structure:
Frontend will be a Winforms app which calls the API in the background by OData. I don't want to rely solely on security in the Winforms app because the API will be accessible from the internet and I can't be sure if a user would not try to access the api with his credentials just to see what is possblie without the "frontend filter". So the permissions lie in the API and if a user tries to access s.t. in the frontend app the app itself "asks" the API if that is possible.
Later on I want to create mobile clients that also use the Odata Web API.
The relevant API in asp.net core are:
IAuthorizationService
AuthorizationPolicy
IAuhtorizationRequirement
IAuthorizationHandler
The authorization pattern you are looking for is called Resource-based authorization
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-2.2
Basically, you can define AuthorizationPolicy, and apply it to a instance of a resource:
var ticket = _ticketRepository.Find(ticketID);
var authorizationResult = await _authorizationService
.AuthorizeAsync(User, ticket, "EditTicketPolicy");
In the authorization handler, you can check if the user is the owner of the resource.
public class ResourceOwnerRequirement : IAuthorizationRequirement
{
}
public class ResourceOwnerHandler
: AuthorizationHandler<ResourceOwnerRequirement, MyBusinessObject>
//: AuthorizationHandler<ResourceOwnerRequirement> use this overload to handle all types of resources...
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
ResourceOwnerRequirement requirement,
MyBusinessObject resource)
{
int createdByUserId = resource.CreatedBy;
Claim userIdClaim = ((ClaimsIdentity)context.User.Identity).FindFirst("UserId");
if (int.TryParse(userIdClaim.Value, out int userId)
&& createdByUserId == userId)
{
context.Succeed(requirement);
}
}
}
//admin can do anything
public class AdminRequirementHandler : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
if (context.User.Claims.Any(c => c.Type == "Role" && c.Value == "Administator"))
{
while (context.PendingRequirements.Any())
{
context.Succeed(context.PendingRequirements.First());
}
}
return Task.CompletedTask;
}
}
BTW, this still can be called claims or role based authorization. Users with specific role can edit their own tickets, but users with admin role also other tickets. The difference is that you apply authorization to a resource, not just action
EDIT:

Hierarchical policies / requirements in ASP.NET Core Identity

I'm just getting started with ASP.NET Core Identity and have the following requirements defined:
public sealed class IsCustomerUserRequirement : IAuthorizationRequirement
public sealed class IsSuperUserRequirement : IAuthorizationRequirement
With the following basic handlers:
public class IsCustomerUserHandler : AuthorizationHandler<IsCustomerUserRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsCustomerUserRequirement requirement)
{
if (context.User.HasClaim(_ => _.Type == "customer"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class IsSuperUserHandler : AuthorizationHandler<IsSuperUserRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsSuperUserRequirement requirement)
{
if (context.User.IsInRole("super_user"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
I can then put these inside basic policies:
services
.AddAuthorization(options =>
{
options.AddPolicy("MustBeSuperUser", policy => policy.Requirements.Add(new IsSuperUserRequirement()));
options.AddPolicy("CustomersOnly", policy => policy.Requirements.Add(new IsCustomerUserRequirement()));
});
And apply it using [Authorize("CustomersOnly")], which works fine.
My requirement is to be able to allow super users, claim principals with the super_user role but without the customer claim, to also access Customers Only areas.
I have currently implemented this by changing the handler to manually check:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsCustomerUserRequirement requirement)
{
if (context.User.HasClaim(_ => _.Type == Claims.Customer) ||
context.User.IsInRole(Roles.SuperUser))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
My issue is this feels like I'm missing the point. Is there a better way to define this so I don't have to repeat the super user check in each handler in future?
The bigger picture in all this is I use IdentityServer4 (ASP.NET Identity-backed) to Authenticate, and then intend to use some JWT-based claims (one claim, two roles) to further identify the user Authorisation falls into an application-specific roles / permissions structure and some custom middleware that has nothing to do with Identity Server. What, if any, best practices are there around this topic?
“this feels like I'm missing the point” – Yes, in a way you are missing the point. You are doing role based authorization: A user can be a customer or a super user.
But instead, the new model is claims based authorization where the user has a claim about something, and you are using that to authorize them. So ideally, the super user would get the same claim the customer gets, and is allowed access to the resource that way. Such a claim also wouldn’t be called customer then, but be rather something that is a property of the user.
You can still use a role-based authorization model with claims but you should probably avoid mixing them. As you noticed yourself, this gets a bit weird eventually.
That being said, there are multiple ways to succeed a policy using different requirements. If you were using roles only (instead of that customer claim), you could simply use the built-in way:
options.AddPolicy("MustBeSuperUser", policy => policy.RequireRole("super_user"));
options.AddPolicy("CustomersOnly", policy => policy.RequireRole("customer", "super_user"));
That way, the CustomersOnly policy would be fulfilled by both customer and super_user roles.
Since you aren’t using a role for your customers, you will have to follow your requirements implementation here. The way authorization requirements work though is that you can have multiple handlers for the same requirement type and only one of them needs to succeed (as long as none fails) for the requirement to be successful.
So you could have your IsSuperUserHandler handle multiple requirements. YOu can follow the AuthorizationHandler<T> implementation to make this work:
public class IsSuperUserHandler : IAuthorizationHandler
{
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (var req in context.Requirements)
{
if (req is IsSuperUserRequirement || req is IsCustomerUserRequirement)
{
if (context.User.IsInRole("super_user"))
context.Succeed(req);
}
}
}
}
So your IsSuperUserHandler is now an authorization handler for both the IsSuperUserRequirement and the IsCustomerUserRequirement. So the CustomersOnly policy that requires the IsCustomerUserRequirement will also be fulfilled for super users.

Understanding MVC5 UserClaim Table

I have been doing a lot of research but none resulted in helping me understand what is the point of UserClaim Table.
When you create a MVC5 project, there are some default tables created upon your database being registered. I understand the purpose of all of them except UserClaim.
From my understanding, User Claims are basically key pair values about the user. For example if I want to have a FavouriteBook field, I can add that field to the user table and access it. Actually I already have something like that built in. Each of my users have "Custom URL" And so I have created a claim in the following way:
public class User : IdentityUser
{
public string CustomUrl { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
userIdentity.AddClaim(new Claim("CustomUrl", CustomUrl));
return userIdentity;
}
}
public static class UsersCustomUrl
{
public static string GetCustomUrl(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("CustomUrl");
return (claim != null) ? claim.Value : string.Empty;
}
}
Above basically allows me to access the CustomUrl by simply calling User.Identity.GetCustomUrl()
The above code won't write to the UserClaims table as the value exists in the Users Table. So what is the point of this table?
I am speculating that maybe I should add CustomUrl to UserClaims and somehow bind that to identity and that may what it is for? I would love to know the answer!
Claims are really useful in cases where you present multiple ways in which your users can register / sign on with your website... in particular, I'm talking about third-party authentication with organisations such as Google, Facebook and Twitter.
After a user has authenticated themselves through their chosen third party, that third party will disclose a set of claims to you, a set of information that describes the user in a way that you can identify them.
What information the claims will contain varies from provider to provider. For example, Google will share the users email address, their first name, their last name but compare that to Twitter... Twitter doesn't share any of that, you receive the identifier of their Twitter account along with their access tokens.
Claims based authentication provides a simple method to facilitate all this information, whilst the alternative may very well have meant creating tables in your database for each individual provider you worked with.

Dynamic User Claims in ASP.NET Identity EF

I'm working on an authentication system that uses ASP.NET Identity with Entity Framework, and I want to have a few claims that are computed values instead of being hardcoded into the claims table.
When a user logs in, how can I add dynamic claims to that login session without actually adding them to the claims table?
For example, I may want to store each user's DOB, but I want add IsBirthday as a claim if the login date matches the user's DOB. I don't want to have to store a "IsBirthday" claim for each user since it changes daily for everyone.
In my code, I use this to log in:
var signInResult = await SignInManager.PasswordSignInAsync(username, password, false, false);
After this is called I can reference the ClaimsPrincipal, but the Claims property is an IEnumerable, not a List, so I can't add to it.
EDIT: I should also mention I am using the Microsoft.AspNet.Identity.Owin libraries.
OK, everyone, I did a bit of digging into the classes provided in ASP.NET Identity and found the one I needed to override. The SignInManager class has a CreateUserIdentityAsync method that does exactly what I was wanting. The following code added the IsBirthday claim to my identity but didn't store it in the database.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
public override async Task<System.Security.Claims.ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
var identity = await base.CreateUserIdentityAsync(user);
identity.AddClaim(new System.Security.Claims.Claim("IsBirthday", user.DOB.GetShortDateString() == DateTime.Now.GetShortDateString()));
return identity;
}
// ... EXCLUDING OTHER STUFF LIKE CONSTRUCTOR AND OWIN FACTORY METHODS ...
}

Categories