I am building a website(front end reactjs, backend asp.net web api core 2) and trying to figure how to do authentication/authorization.
For authentication I pretty much will JWTBearer tokens, if the username and password match what I got for the user in my db, give them a token.
It is for authorization that I am not sure about, The last time I had to do something it was more have roles in your database and then check if that user had that role or not when they tried to access something.
Now I was looking at this blog and the author talks about
Rather than try and store all the “roles” that a user might have (e.g. administrator, user, super user) you can store information about the user as claims.
I am confused about this, how does this work. Say I need one user to be able to see the very secret area of my site(ie admin area) but another user who is also logged in can't see it as he is a general user.
What information is used to make this claim? What is being stored in the database?
With roles, you could have something like User can have a Roles (ie Admin) and then just check that when they try to do something.
I am also wondering how would the front end know what to hide(ie the link that says "admin area") as can you send back a result saying that they have a claim that allows them to see the admin area?
Claims can be used to manage a wider array of use cases than Role, particularly when paired with Policies and Requirements. Two examples that come to mind:
Requiring a user have a specific reputation in order to edit a record
Requiring a user to be at least 21 to order a particular product
One could create an "Over21" role, and a "Over1000Reputation" role, but maintaining role membership becomes unwieldy; you need to set up jobs to add (or remove) users from those roles as their age or reputation changes.
Instead, have your JWT include a Reputation and BirthDate claims:
identity.AddClaim(new Claim("Reputation", 722));
identity.AddClaim(new Claim("BirthDate", new DateTime(2000, 1, 1)));
Your code could then implement a Requirement and AuthorizationHandler that calculates whether the user should be authorized based on these claim values:
public class ReputationRequirement : IAuthorizationRequirement
{
public int Reputation { get; private set; }
public ReputationRequirement (int reputation)
{
Reputation = reputation;
}
}
Paired with an AuthorizationHandler along these lines:
public class ReputationHandler : AuthorizationHandler<ReputationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ReputationRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == "Reputation"))
{
return Task.CompletedTask;
}
var reputation = context.User.FindFirst(c => c.Type == "Reputation").Value as int;
if (reputation >= requirement.Reputation)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
In your startup's ConfigurationServices, you can give a name (Policy) to a particular instance of your requirement:
services.AddAuthorization(options =>
{
options.AddPolicy("Reputation750", policy => policy.Requirements.Add(new ReputationRequirement(750)));
options.AddPolicy("OldAndWise", policy =>
{
policy.Requirements.Add(new ReputationRequirement(750));
policy.Requirements.Add(new MinimumAgeRequirement(40));
}
});
Lastly, you can mark MVC controllers or methods with a policy:
[Authorize(Policy = "OldAndWise")]
public class StackOverflowWineController : Controller
{
// omitted for brevity
}
Note the example linked to has more detail that this brief code snippet.
Regarding storage of claims in a database, that depends on the use case. When treating Roles as Claims, you would still have something like a UserRoles and RoleMembership tables. For Reptuation and BirthDate, it would probably make more sense to store them in a User table, rather than some sort of dedicated generic Claims table.
Roles can still be used like in the older versions. Claims are simply a way to store more user data and provide more flexibilities with the authentication. You can add them to a list like this:
List<Claim> claims = new List<Claim> {
new Claim(ClaimTypes.Name, "username"),
new Claim(ClaimTypes.Role, "First Role"),
new Claim(ClaimTypes.Role, "Second Role"),
new Claim(ClaimTypes.Role, "Third Role")
};
You can authorize any controller or action using [Authorize(Roles = "user, superUser")]. If you want to display something to a user based on roles, you can use #if (User.IsInRole("User")).
As of JWT creation, you can have a look at this class: https://github.com/neville-nazerane/netcore-jwt-sample/blob/master/website/TokenGenerator.cs
It has utility functions to create a JWT with a given user name and also one that takes in a username and a comma separated string of roles.
Related
I'm trying to add and delete claims (roles in this case) dynamically after a successful login using Identity 2 in Asp.Net 4.5.2. My application has an authentication database which contains the AspNetUsers, AspNetRoles and AspNetUserRoles tables etc and a number of other databases. During the course of a user session my users can switch between the other databases and their current claims (roles) are modified based on which database they are currently using. I'd like, therefore, to add and delete claims throughout the session. This allows me to modify which views the user has access to based on their current authorizations.
I've done many days of research on this in stack overflow and the MS Identity help pages, such as they are, and can find nothing similar to what I'm attempting to do. Based on what I've learned I've been able to add my own new claims but only during the login process, changing them at any other point works for that request but the changes do not persist and are lost by the time the next request comes in.
As far as I can tell when the claims are added during login they are encoded within the session cookie and when I add them at any other point the cookie is not modified. From my current understanding this is happening in the Identity module of the OWIN pipeline. The method where I've successfully added claims is the GenerateUserIdentityAsync method in ApplicationUser (derived from IdentityUser.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("Customer_ID", Convert.ToString(this.Customer_ID)));
userIdentity.AddClaim(new Claim("LastName", this.LastName));
userIdentity.AddClaim(new Claim("FirstName", this.FirstName));
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "TestRole", null, null, "TestIssuer"));
return userIdentity;
}
Unfortunately, this doesn't help my case because I'm attempting to add/delete the claims outside the OWIN pipeline.
Is what I'm trying to do possible at all or am I going about this entirely the wrong way? I would have thought dynamic modification of authorizations is not that unusual.
Some of the many stack overflow questions I've looked at include:
How to add claims in ASP.NET Identity,
Dynamic User Claims in ASP.NET Identity EF and
ASP.NET Identity and Claims.
None of them quite cover what I'm attempting to do.
In Asp.net :
Adding claims to existing identity seems like a small task to accomplish. But, well, it doesn’t go so easy. We can build a middleware class and try something like shown here.
foreach(var role in user.Roles)
{
var claim = new Claim(newIdentity.RoleClaimType, role.Name);
identity.AddClaim(claim);
}
And in Asp.net core:
There’s the correct way to edit existing identity and it’s called claims transformation. Basically, we have to write a custom class that implements the IClaimsTransformation interface. Documentation doesn’t give much information about it but the most important thing is said – we need to clone the given identity.
In short, here’s how the process goes:
Clone current user identity
Add custom claims
Return cloned identity
public class AddRolesClaimsTransformation : IClaimsTransformation
{
private readonly IUserService _userService; public class AddRolesClaimsTransformation : IClaimsTransformation
{
private readonly IUserService _userService;
public AddRolesClaimsTransformation(IUserService userService)
{
_userService = userService;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// Clone current identity
var clone = principal.Clone();
var newIdentity = (ClaimsIdentity)clone.Identity;
// Support AD and local accounts
var nameId = principal.Claims.FirstOrDefault(c => c.Type ==
ClaimTypes.NameIdentifier || c.Type == ClaimTypes.Name);
if (nameId == null)
{
return principal;
}
// Get user from database
var user = await _userService.GetByUserName(nameId.Value);
if (user == null)
{
return principal;
}
// Add role claims to cloned identity
foreach (var role in user.Roles)
{
var claim = new Claim(newIdentity.RoleClaimType, role.Name);
newIdentity.AddClaim(claim);
}
return clone;
} }
The final thing to do is to register claims transformation with dependency injection in the ConfigureServices() method of the Startup class.
services.AddScoped<IClaimsTransformation, AddRolesClaimsTransformation>();
enter code here
Based on https://gunnarpeipman.com/aspnet-core-adding-claims-to-existing-identity/
I'm trying to find out what role(s) a user has within a Blazor Server app that has Authentication setup for an organization that uses MS accounts and Azure Active Directory. I would imagine that the roles are set by the IT Operations team managing the MS accounts, but I'm not finding where to verify that at.
So I would like to know: where are roles set when using Org authentication, how can I change them, and is there a simple way to see what roles an authenticated user has?
I tried looking for an authenticated user's role in the MS doc example code using the cascade parameter like below:
#page "/"
<button #onclick="LogUsername">Log username</button>
#code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
LogUsername();
}
private async Task LogUsername()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
var test = user.Claims.Where(c => c.Type.Contains("Role")); // also tried "role"
string role = test.FirstOrDefault().ToString();
Console.WriteLine($"{role} is authenticated.");
}
else
{
Console.WriteLine("The user is NOT authenticated.");
}
}
}
Identity has a RoleClaimType but I can't find anything else that would tell what the Role for a given user is. Am I looking in the wrong spot for this maybe? I couldn't find any detailed docs on how Organiztion Authentication works with Identity in .NET core and Blazor, so any tips, advice, or nudges in the right direction is appreciated!
Roles are set in the azure portal. Azure Active Directory -> App Registrations -> [your app] -> Roles and Administrators. For custom roles, your organization will need Azure AD Premium P1 or P2 licenses.
As for how to retrieve them, here is a quote from documentation:
Get role claims. When a user signs in, the application receives the
user's assigned role(s) in a claim with type
http://schemas.microsoft.com/ws/2008/06/identity/claims/role (the
roles claim in a JWT token).
A user can be assigned multiple roles, or no role. In your
authorization code, don't assume the user has exactly one role claim.
Instead, write code that checks whether a particular claim value is
present:
if (context.User.HasClaim(ClaimTypes.Role, "Admin")) { ... }
var Roles = User.Claims.Where(t => t.Type == System.Security.Claims.ClaimTypes.Role);
for those who are looking like me, in .Net 6
official documentation how to enable roles in Blazor learn.microsoft.com
close the component for viewing
<AuthorizeView Roles="Admin">
<div> ..... </div>
</AuthorizeView>
get the name of the user's first role
<AuthorizeView>
<Authorized>
<p>#context.User.Claims.FirstOrDefault(c=>c.Type == "role").Value</p>
</Authorized>
</AuthorizeView>
close page from view
#attribute [Authorize(Roles = "Admin, Normal")]
So I’m using firebase for authentication on my WebAPI app. It gets a JWT token and then authenticates it and then puts the claims in the HttpContext.User.
My only problem is I just want to use Firebase Authentication for authentication, not for authorization. I want to use ASP.NET Identity for this.
so right now, when someone connects to my server, I’ll check if they don’t have an account, if not I will create one for them.
var name = _contextAccessor.HttpContext.User.Claims.First(c => c.Type == "user_id").Value;
ApplicationUser user = await _userManager.FindByNameAsync(name);
if (user == null)
{
user = new ApplicationUser(name);
await _userManager.CreateAsync(user);
}
So this works, and now there is a record in the ASPNetUsers against the user and later on, I can give it Claims and Roles against whatever business rules I’d like.
However, my question is, previously, when I’ve used ASP.NET Identity I’ve been able to leverage all of the built-in features like the Authorize attribute to do my authorization.
So if both authentication and authorization is done using ASP.NET Identity, I can write
[Authorize(Roles = "Administrator")]
Obviously, that won’t work with external authentication because HttpContext.User is the Firebase Authenticated user, not the corresponding ASP.NET Identity user that has the Administrator role.
Is there a way to customize the existing Authorize attribute to configure it to somehow convert my firebase token into an ASP.NET Identity so that it would recognize the roles and claims it has or if I wanted to do all this through middleware, am I going to need to write my own authorize attribute?
Ok so I've finally figured it out. It seems there's a couple of events you can intercept during the Jwt authentication process. In particular, there is an OnTokenValidated event.
services.AddJwtBearer(options =>
{
...
options.Events = new JwtBearerEvents
{
OnTokenValidated = async ctx =>
{
// 1. grabs the user id from firebase
var name = ctx.Principal.Claims.First(c => c.Type == "user_id").Value;
// Get userManager out of DI
var _userManager = ctx.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
// 2. retrieves the roles that the user has
ApplicationUser user = await _userManager.FindByNameAsync(name);
var userRoles = await _userManager.GetRolesAsync(user);
//3. adds the role as a new claim
ClaimsIdentity identity = ctx.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
foreach (var role in userRoles)
{
identity.AddClaim(new System.Security.Claims.Claim(ClaimTypes.Role, role));
}
}
}
};
});
So what the code above is saying
once the token is authenticated, take the userId from the
external provider
Go into ASP.NET Identity and find the user and
the roles for that user [remember in my original question, I insert a user
into ASP.NET Identity table when they first log in, I'm just grabbing that user]
Insert the roles of that user back into the ClaimsIdentity
The result is that when the Authorise attribute runs, it'll include the AspNET Identity Roles of the user in the check and I can do something like below and it'll check the role.
[Authorize(Roles = "Administrator")]
Yes you can use the authorize attribute with some modification,
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme,Roles = "Administrator")]
But if you want to make it a default scheme, then refer to this answer
Here is a summary, Add it to he Startup class,
services.AddAuthentication(cfg =>
{
cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
I think this should make your Authorization work:
You have to make ClaimsPrincipal the base class of your ApplicationUser class.
(ClaimsPrincipal implements the IPrincipal interface)
Then inside your ApplicationUser class implement/override the IsInRole method that comes with the ClaimsPrincipal (base) class. IsInRole official documentation. The role argument of the method will contain the value of the Roles property on the AuthorizeAtribute that you specify.
Set the HttpContext.User using your instance of type ApplicationUser. (explanation: Setting the property User on Httpcontext allows you to specify (=customize) the user of the current request within asp.net request processing)
I have an ASP.NET core MVC site with a SQLite data connection using ASP.NET core authorization like this:
// Startup.ConfigureServices
services.AddAuthorization(e =>
{
e.AddPolicy(Policies.UserRead, b => b.RequireRole("Editor", "Root"));
}
This is one of the policies which restricts access to site with user information. (Policies.UserRead is a constant string). This policy is then applied to the view like this:
[Authorize(Policies.UserRead)]
public async Task<IActionResult> Index()
{
}
This works great and only users with the role Editor or Root can access the view. But problems arises when the role of a user is changed while logged in. e.g.
User A (role Editor) logs in and uses Index() - success
User B (role Root) logs in and removes the role Editor from user A
User A uses Index() - still success
You would expect that user A can not access Index() anymore because he no longer as the role Editor. Buuuut he still can - as long as he does not log out and log back in again, because relogging fixes this issue. It seems like somebody (I think ClaimsPrincipal is the culprit here) cached the role - which would be OK if I knew how to invalidate the cache...
Role changing code:
// get the user whos role is changed
var targetUser = await _context.Users.SingleOrDefaultAsync(m => m.Id == model.Id);
if (targetUser == null) return NotFound();
// get the user who changes the role
var sourceUser = await _userManager.GetUserAsync(User);
if (sourceUser == null) return RedirectToAction("Index");
// remove the current role
await _userManager.RemoveFromRoleAsync(targetUser, targetUser.Role.ToString());
// add to the new role
await _userManager.AddToRoleAsync(targetUser, model.Role.ToString());
// update & save the changes
_context.Update(targetUser);
await _context.SaveChangesAsync();
This is basically the code I use to change the role of a user (I cut out the view/model parts because they are irrelevant). Notes:
targetUser and sourceUser are both ApplicationUser (which implements IdentityUser).
_userManger is - who would have thought - of the type UserManager<ApplicationManger>
I tried to relog the user using SignInManger<> but it seems like you can only log out the current user - which would be the user changing the role and not the user whose role would be changed.
What am I missing? It would be nice if the user would not have to do anything (e.g. sign back in) in order to "refresh" the user role.
The problem is that the user's role claims are stored in the cookie(default implementation of aspnet identity) so unless the user sign out even if the user's roles change, authorization result does not change. The solution is to use ValidateAsync event. Example exists in the official docs.
Another possible solution is to exclude role claims from cookie and use claims transformation.
For this you need to override the CreateAsync method of UserClaimsPrincipalFactory see this article how to change claims. Then you can use claims transformation to add role claims.
I'm writing a simple chat application using ASP.NET MVC 5 and SignalR. The application doesn't require any complicated authentication logic. User simply enters their login and enters the system (if there was no such user in the db before, it's created).
My intent was to use Session to hold the logged in user and their information (id from the database and login/username) and write a global filter to check if user is authenticated on each request. I've got some problems with SignalR though. It's not possible to access the Session from the SignalR Hub, while I need it to find out the login of the user who sent the message.
As fas as I found out, it's possible to work with the User.Identity using SignalR's Context. However, in my case Uder.Identity is completely empty. Presumably because I've created the app as 'no authentication' and the mechanism that User.Identity uses to get user data is not aware of my manipulation with session.
The question is, is it possible to elegantly intergate User.Identity into my application and make it aware of the Session? Creating ASP.NET MVC project with individual user accounts creates a mess with stuff like
public AccountController() :
this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
and that's what I don't want to have in my application by any means, since I want to write it as clean as possible and not use any solutions I am not familiar with. I also don't need any external login providers, cookies, etc.
I was thinking about implementing some in-memory storage on my own. However, I would still have to clean this store up at some point of time. I though of cleaning it up when the Session_End event is fired. However, this event will only be fired if there is data in Session which I don't want to have since it would be quite awkward to have standalone in-memory storage and rely on Session events to clean it up and, moreover, to set some data in Session just to make sure Session_End will fire.
Here's the solution I came up with. It's still not as clear as I would like it to be and it uses cookies, so any additions are welcome.
First of all, I had to install Microsoft.AspNet.Identity.Owin package and all its dependencies.
Then I registered my auth as follows:
private void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
This method is then called in Configuration method of Startup.cs file.
In order to work with the authentication, an instance of IAuthenticationManager is required. I inject it into my controller and use Ninject to resolve the dependency
kernel.Bind<IAuthenticationManager>().ToMethod(_ => HttpContext.Current.GetOwinContext().Authentication).InRequestScope();
Here's the Login method of Account controller which user is redirected to when auth is required (thanks to LoginPath in ConfigureAuth method):
[HttpPost]
public ActionResult Login(LoginViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = authenticationService.AuthenticateUser(model.Login);
IdentitySignIn(user.Id, user.Login);
return RedirectToAction("Index", "Home");
}
AuthenticationService is my own class which communicates with the database and performs the login to create or return a user.
IdentitySignIn is declared as follows:
private void IdentitySignIn(int userId, string userLogin)
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.PrimarySid, userId.ToString()));
claims.Add(new Claim(ClaimTypes.Name, userLogin));
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties()
{
ExpiresUtc = DateTime.UtcNow.AddDays(200),
IsPersistent = true
}, identity);
}
This method creates a cookie with appropriate info. There is one thing, though. When I check the cookie expiration date, it's not the current date plus 200 days, which is kinda awkward.
SignOut method is quite simple:
public void IdentitySignout()
{
authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
}
So, User.Identity is now accessible in the SignalR hub with the Identity.Name property.
To do: it would be also nice to get access to the Id property via something like User.Identity.Id. As far as I know, it requires implementing custom Principal.
I am also still thinking of implementing some sort of session of my own using cookies to store the session id on client side, though it will definitely take more time than using Identity.
Addition:
in order to get user id, one might use the extension method of IdentityExtensions:
(Inside the Hub)
Context.User.Identity.GetUserId()
In order for this to work, the Claim with the value of user's id should have the type ClaimTypes.NameIdentifier.
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, userId.ToString()));
claims.Add(new Claim(ClaimTypes.Name, userLogin));
Update 2:
Here are some additional links on the subject that greatly helped me. I do not include links to MS guides since they are quite easy to find.
http://leastprivilege.com/2015/07/21/the-state-of-security-in-asp-net-5-and-mvc-6-claims-authentication/
http://weblog.west-wind.com/posts/2015/Apr/29/Adding-minimal-OWIN-Identity-Authentication-to-an-Existing-ASPNET-MVC-Application