does Authorize attribute with Roles take Claims into account - c#

I tried searching everywhere on the web, but I can't seem to figure out this important part.
Basically, if we do a DB call each time when checking if a user belongs to a role - this will have negative effect on performance.
I saw code examples listing all user roles, e.g.
var roles = ((ClaimsIdentity)User.Identity).Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value);
the code can be used in controller action, it is also possible to fetch claims the same way in an Attribute Filter.
From this example I infer that Claims come into play (seems to be most performant solution).
I tried to find out if Authorize attribute with Roles verifies user's claims, but the official Microsoft documentation doesn't cover this bit.
AuthorizeAttribute class
Specifies that access to a controller or action method is restricted to users who meet the authorization requirement.
Properties:
Roles - Gets or sets the user roles that are authorized to access the controller or action method.
And that's the extent of what we have.

Both the Authorize attribute as e.g. User.IsInRole look at the User.Identity roles claims.
By default the Authority (where the user logs in) will add the roles from the AspNetUserRoles table as claims of type 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role'. See WIF claimtypes members.
The client app will automatically take the information from the token / cookie and convert this as User.Identity. As the claim type matches, the role type claims are mapped as roles.
This means that the app doesn't need access to the user store. In most cases this is also not possible. So it is actually not about performance, but about accessibility. Usually apps don't have access to the Identity context. So UserManager is not an option.
There is however a drawback when using claims. The information is dated. When the user logs in a snapshot of the claims at that time are added to the Identity. If in the meantime claims (or roles) are updated in the database, then these changes are not noted. Only after the user logs in again, the changes become effective.
This means that claims are only suitable for pieces of information that do not (frequently) change unless you find a way to invalidate claims. But that would probably mean to access the database or call the authority.
That's why I wouldn't recommend the use of roles. As roles tend to be used for authorization, but you can't revoke access in the meantime. So until you solve that, you may want to consider an alternative.
Sticking to UserManager is not an alternative, because the context may not be available for all apps.
That's why resource-based authorization may be a solution for you. Please read my answer here for additional thoughts.

Open your Startup file and change this:
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
to this:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
Then the Roles should start working.

Related

Asp.NET Core Claim based Authorization

I was using Role-based authorization for asp since now. I wanted to learn Policy-based and claim-based authorizations. Then I realized I can turn claims into something like "Permission based api authorization" which I am using for NodeJs. I have 2 questions to understand the fundamentals, thank you.
Question 1: The scenario in my mind is this: Users have roles, and roles have claims. Then I will add Authorization policies which require claims. Is this a correct approach? Basically users won't have claims. (I have seen many approaches on net, that's why I am asking)
Question 2: To do that I created tables with switches. I will add a photo to so you can picture the scenario easily.
But when I use:
var role = await roleManager.FindByIdAsync(RoleId);
await roleManager.AddClaimAsync(role,new Claim("Permission 1", "perm explanation"));
List<IdentityRoleClaim<string>> allClaims = _db.RoleClaims.ToList();
this block of code doesn't check duplication check for Claim which will be added to database. We can use RoleManager and UserManager, but is there a Manager for claims? Thank you again.
There's many way to solve authorization problem, what you are asking tended to optionality, best solution would be difference based on various usecases and the developer, who implement them themself. Since I was use all role, claim and policy, I'll giving my answers as advises.
The scenario in my mind is this: Users have roles, and roles have claims. Then I will add Authorization policies which require claims. Is this a correct approach? Basically users won't have claims. (I have seen many approaches on net, that's why I am asking)
Think of role as A BIG CLAIM. Whenever you saw a role, you grant them permission to do some stuffs. Like a when you entering a building, the security would have the permisions to check your basic personal information. It doesn't matter who the security is, they have the permisions to ask for your paper as long as they performed in security role.
To do that I created tables with switches. I will add a photo to so you can picture the scenario easily.
Have you saw something in jwt token like
"someCustomClaim": ["ClaimOne", "ClaimTwo", "ClaimThree"],
Think of how this claim will be preserve in a relational database like sql, that's why there is not an unique index there.
But, you can always add one, and enforce each claim are unique, or each role+claim is unique,... all is up to your use cases.
We can use RoleManager and UserManager, but is there a Manager for claims?
Claims doesn't make any senses if they stand alone, it has to be attached with Role or User to be meaningful right ?
So UserManager(which have claims via UserClaims) and RoleManager(which have claims via RoleClaims) are good enough, even if you in a very-complex scenerio that require both Multiple Role - Claims and Claims that directly assign to an user.
And about Policy, that's the mixin way to solve complex authorization scenerios, like UserAuthenticated + Must have Internal Role + Must have Maintaination Claim + does not have fresher claim. If you need to authorize an endpoint kind of complex like this... you might consider to register those as policies.

Fetch permissions from identity server during authorization

I am using identity server 4 for authentication and authorization, and user permissions are saved in JWT and then used on API-s to check if users has required permission.
But the problem is that JWT got too big and I would like to remove permissions from it, and make custom authorization on API-s so that its fetches permissions from identity server instead of getting it from JWT.
API would get only userId from JWT and then based on that fetch additional information from identity server. Is it possible to do something like that?
We basically have a similar problem in our application.
The way to solve this problem is using an event which is raised at the level of the API resource (the API which you are protecting by using JWT bearer tokens authentication) once the JWT token has been read from the incoming request and validated.
This event is called OnTokenValidated, see here for more details.
This is the top level plan:
keep your JWT bearer token minimal. At the very minimum it contains the subject id, which is the unique identifier of the user at the identity provider level. You can put other claims there, but the idea is that the JWT bearer token must be small
implement a way to get the user permissions given the user unique identifier (you can use the subject id as an identifier or any other id which makes sense in your system)
make the user permissions fetch mechanism of the previous point accessible via api call. Caching this API is a good idea, because usually permissions are stable. Defining a smart way to evict this cache is beyond the scope of this answer, but it's something you should definitely think about.
once you have fetched the user permissions (via an API call) you need to make them available to the ASP.NET core authorization framework. The simplest way to do so is create a custom claim type (for instance: "app_permission") and create one user claim per each user permission. Each of these permission claims has the custom claim type ("app_permission") and the permission name as the claim value. For instance a user having the two permissions "read-content" and "write-content" will have two claims both having "app_permission" as the claim type, the first one having "read-content" as the claim value and the second one having "write-content" as the claim value.
the permissions claims defined at the previous point can be injected in the user identity (at the API resource level) by defining an additional ClaimsIdentity for the user and by adding it to the current user identity. The process depicted here is quite similar to a claims transformation done by an MVC application using cookie authentication.
In the Startup class of your API resource, in the point where you register the authentication services, you can do something like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:8080";
options.Audience = "sample-api";
options.RequireHttpsMetadata = false;
// register callbacks for events
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
if (!context.Principal.Identity.IsAuthenticated)
{
return;
}
var subjectId = context.Principal.FindFirst(JwtClaimTypes.Subject)?.Value;
if (string.IsNullOrWhiteSpace(subjectId))
{
return;
}
// do whatever you want with the user subjectId in order to get user permissions.
//You can resolve services by using context.HttpContext.RequestServices which is an instance of IServiceProvider
//Usually you will perform an API call to fetch user permissions by using the subject id as the user unique identifier
// User permissions are usually transformed in additional user claims, so that they are accessible from ASP.NET core authorization handlers
var identity = new ClaimsIdentity(userPermissionsClaims);
context.Principal.AddIdentity(identity);
}
};
});
+1 for the accepted answer but I would just like to offer an alternative solution to this problem. If your permissions are pretty simple like readResource or writeResource then you could define all your permissions as enum and use integers instead of strings in JWT, that would reduce JWT size.
If permission list is still huge then you could also group permissions together so that the permission list is smaller for some customers e.g. merge readResource, writeResource, updateResource, deleteResource into one permission called crudResource.

.Net Core Identity Framework Get Users By Claim

In our .NET Core Web API, we have configured claim based authorization and it works perfectly. We have created role-claims and assign roles to users. We do not use user-claims.
Now I have a requirement to get users having a specific claim. In UserManager there is a method GetUsersByClaimAsync(Claim), which seems only considering user-claims. Not role claims. So in my case, it is not usable.
So I was thinking of getting roles by claim and then get the users by roles (not very efficient as it has to make several DB calls to get the users of each role separately). But even to do that, there is no straight forward method to get roles by claims in UserManager or RoleManager.
So to me, it seems there is no clean way of achieving this other than custom querying.
I'm not sure I'm missing something here. Has anyone achieved this, using a better way?
Thanks
Now I have a requirement to get users having a specific claim. In UserManager there is a method GetUsersByClaimAsync(Claim), which seems only considering user-claims. Not role claims. So in my case, it is not usable.
I think you could try the UserManager<TUser>.GetUsersInRoleAsync(roleName) api:
var users = await _userManager.GetUsersInRoleAsync("Role1");
By the way, to use the above api, you need enable the Role related services by :
AddDefaultIdentity<IdentityUser>(options => { /*...*/ })
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>();

.NET #claims == 0 on all users

I am working on adding a user manager module to an application. It has a database and this database has a table of users and a table of user claims so that each user can have mulotiple claims. However, when I use usermanager to get Users.ToList() each user has its claims collection set to 0 entries even if the user in question does have claims listed in the user claims table.
It appears obvious to me that for some reason just doing usermanager.Users.Tolist() does not cause the code to consult the user claims table to check what claims each user has.
So the question is where and how to insert code that does exactly that.
Some potential issues with the existing code:
1. They have made a subclass of the IdentityUser so the UserManager is defined to be a usermanager of that subclass. But there is no explicit subclass of the usermanager. I am contemplating creating a subclass of UserManager for that purpose and if so I could override the Users property so that reading the Users list caused it to populate each user entry with the claims for that user. Does that sound like a clean way of doing it?
In some other stack overflow question I saw a reference to a Startup.Auth.cs file. We have a Startup.cs file but no Startup.Auth.cs file, should we make one?
Thank you in advance for any input.
If you used the ASP.Net identity template when you created your project, you must have a Startup.Auth.cs, it could be hidden as a sub-file of Startup.cs, or in the App_Start folder etc.
If trying to bypass the UserManager, when you request your user from EF, make sure you .Include(...) the claims property. For example:
dbcontext.Users.Include(u => u.Claims).FirstOrDefault(...);
Better yet, you should be using UserManager:
var userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
var claims = userManager.GetClaims(userId);

ASP.Net MVC 5 OAuth2 Facebook Claims are not persisted. Why?

I just created a new MVC application to learn something about OAuth2. To get started I registered as a developer with Facebook and enabled the Facebook Identity Provider in my application:
Startup.Auth.cs
app.UseFacebookAuthentication(
appId: "something",
appSecret: "secret");
Authentication worked straight out of the box but I wondered if my User property of my Homecontroller had any Facebook related information. It had not and I wondered why. I had a look at the Katana/Owin sources how the Facebook provider is implemented and found a place where custom claims are being set:
FacebookAuthenticationHandler.cs
context.Identity.AddClaim(new Claim("urn:facebook:name", context.Name, XmlSchemaString, Options.AuthenticationType));
But when I reach my Controller and check the Identity's Claims property there are none of these custom claims once set by the Owin provider. When I check the
database (AspNetUserClaims table) I find that completely deserted.
Is there a reason why claims are not persisted to the db? Is the db even a good place to store these claims (at least there is a table)? Do I have to configure anything in my application to get the claims presisted? Or is there another point where I can keep the claims to be alive for the whole user session?
Allright, dove a bit deeper into Owin and came to the conclusion that the Authentication Providers are part of an Owin middleware construct which runs independently in a pipeline and does not affect the applications principal directly. There is an action in the AccountController named ExternalLoginCallback which is called when the whole auth aria is finished. It gets an ExternalLoginInfo object which holds all my claims and constructs an ApplicationUser which acts as the official application user. It has a Claims property but that one is not automatically populated with my claims from the ExternalLoginInfo and I guess the reason is that claims could change with every login. So in my case I would better delete all old old claims before I add the new to my user. Since this is custom I have to provide that somehow by myself. Since this data is very volatile I keep that in Session for now.
Any comments on that issue are warmly welcome.

Categories