Hi we're having an issue with ASP.NET Identity.
We have an admin page that allows us to affect users to roles. (Admin etc).
The issue is when we affect a role to someone it doesn't apply for him until he logs out/logs back in.
We tried putting
services.Configure<SecurityStampValidatorOptions>(options =>
{
options.ValidationInterval = TimeSpan.Zero;
});
But it causes too many issues with cookies being passed at each request etc.
Is there any way to either :
Force logout another user
Refresh his claims without relogging?
The only solution I have in mind atm is to use something like a list of users to be updated somewhere (redis for exemple) and check this list in a middleware and refreshSigninAsync() if the user is in the list.
You were close, but I'd recommend you change the configuration.
I've added additional explanation on why you want to do this, in case someone wants to understand the thought process.
The problem:
When your user signs into the app they receive a cookie, but when an account is logged out, the user still has the valid cookie, so they aren't really signed out.
How to force sign-out:
Since a cookie gets re-used for as long as it's valid, you need a way to invalidate a cookie. You invalidate a cookie through a security stamp.
Configure Identity so that it periodically re-validates the cookie. It compares the security stamp in the cookie to the database version, and, if it fails, your user needs to reauthenticate.
Add the following configuration to your services:
services.Configure<SecurityStampValidatorOptions(opts => opts.ValidationInterval = System.TimeSpan.FromMinutes(1));
Now, you simply have to use the UserManager to find the user in the userstore, update the roles (or do whatever), then call the UpdateSecurityStampAsync(user) method. For example:
public async Task<IActionResult> OnPostUpdateRolesAsync(string userId)
{
var user = await _userManager.FindByIdAsync(userId);
// do stuff
await _userManager.UpdateSecurityStampAsync(user);
return RedirectToAction(nameof(SomePage));
}
Now that causes the user to be signed out the next time the cookie is revalidated and is forced to re-authenticate.
Notes:
This is certainly a destructive method in terms of user experience, since any HTTP request that the user does will get terminated afterwards.
Also, it's not really an immediate sign-out, only from the perspective of the application is it immediate. The user does not know when he has been signed out, that will only be apparent when he makes an http-request.
Related
I'm going to get so many "okay grandpa" comments for this one.
I've read a dozen articles and every SO question I could find on this subject.
I must have been away too long or missed something completely, because I swear that user authentication used to be very simple. I seem to recall built-in methods and a session on the server simply knowing who the user was via a cookie or similar, with the ability to store information "in session". I don't recall even setting up authentication in years past, it was just built-in to new applications.
Instead, the most succinct guide I could find is very involved. I think I need a token authorization/authentication setup because there may be consumers (like apps) who don't have a typical cookie pattern these days. In my head, the token works like a cookie except it's manually held on the user end and passed via header with each request?
To its credit, the guide worked, at least for logging in and correctly utilizing the simple Authorize attribute in controllers. However, User.Identity.Name is always empty, even when User.Identity.IsAuthenticated is true, which is perplexing.
How I think auth is working:
User request hits API with username/password
Service checks the combination, and returns an encrypted JWT to the user
The user sends the JWT back with every request
The server decrypts this JWT to identify the user - this is probably where I'm wrong
So here is where my question comes in:
I need more data about the user, like access to the entire UserModel with every request, but I don't want to go to the database to find it every time. This is where I think there should just be a session object in memory, but that doesn't appear to be the case with token authentication.
TL;DR:
Where do I put user-specific, short-term ("session") information for consumption in future requests where a user is identified with a JWT in the Authorization header instead of a cookie?
Session state isn't right, because it's hard-wired to a cookie
HttpContext.Items aren't right, because it's just for the one request
Cache storage isn't right, because it's not user/session specific. I could potentially create a session-like user-keyed storage here but that seems way, way over-engineered for this.
Basically anything where I'm passing all the data (not just a user identifier) to the client then relying on the client to pass it back seems wrong? But feel free to correct me.
The server decrypts this JWT to identify the user This is probably
where I'm wrong
The JWT token is not encrypted, its signed so you can't alter it. You can open it if you look at jwt.io for example.
Where do I put user-specific, short-term ("session") information for
consumption in future requests where a user is identified with a JWT
in the Authorization header instead of a cookie?
You put it in the principle claims of the token. In the guide you linked it wrote:
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.NameId, user.UserName)
};
So you add whatever you want to the claims to store it on the token and later you can access this data via:
var claim = _contextAccessor.HttpContext.User?.Claims.FirstOrDefault(d =>
d.Type == ClaimTypes.NameIdentifier);
You also can't use any of these other examples that you listed like HttpContext.Items because those are not signed. If the token is altered in any way the system identifies this and returns a 401
Sorry for my bad English. I'm writing an application in ASP.NET Core using Vue.JS for client-side. For authenticate user I'm using JWT and ASP.NET Identity. I have a method for change the password. But I can't understand: How to invalide token after password change? I want that the user authenticated in another browser will logout after that. Is there a man who haved a problem like this?
You normally don't invalidate JWT's because they are meant to be short-lived access tokens and therefore after the password change, request for new token will prompt the user to reenter credentials.
If you do absolutely need to invalidate the JWT immediatelly after password change - you need to look into Introspection where your backend api essentially has a backchannel to your token issuer and it can then re-validate token every request. This way if you invalidate token at the issuer side - it will reflect on the api side immediately.
I've been thinking about this and the inability to invalidate a JWT that's already out there may not be built into anything, but is possible.
Here's the narrative: You have an alarm system installed that can be controlled via web and your ex-S/O is logged in to your previously shared account. They are upset and they keep enabling the alarm at random times.
If the web app uses JWTs to store session, you could change your password but the JWT your ex possesses will still be usable for a period of time until the timeout is reached.
Solution 1: short timeout. but what if you want to stay logged in for longer periods (such as a password manager)
Solution 2: logout ALL users by changing the Signing Key of your Certified Authority, basically invalidating ALL JWTs across the board. This is still a less ideal route as I'm sure you can imagine.
Solution 3: track the current JWT for each user in your Users table. If the JWT they possess is different from the current one, then they aren't authenticated. If the user logs out, nullify the stored JWT-data in your Users table which would equally unauthenticate JWTs for that user and force a relogin.
I'd also recommend storing a bool of "logged in" for the user. DO NOT RELY ON THIS. This would be a value to set to true when they log in, set it to false when they log out, and validate the value is 'true' if they ever pass you a JWT. This will ensure that the moment they logout they are forced to reauthenticate.
Assuming you go with solution 3:
When storing JWT data for this solution, I'm leaning towards not storing the entire JWT because it's rather large text to begin with. Alternatively just store the JWS (JWT Signature) which will make the stored value both smaller and unusable if captured for any reason.
Next, it's a hash to begin with so we could just store the last maybe 9 values (9 because int32 max is 2147483647). We just need a bit of uniqueness, not much.
Next, we could avoid the string comparison for validating that the JWS passed is the active one if we use regex to pull the integers out of the JWS and again take maybe the first 9 numbers you encounter.
Following this method, and returning to the narrative, if you were to log out your user would be marked as logged out resulting in both yourself and your S/O being required to reauthenticate. (assuming you've changed your password you're golden, otherwise it's time to contact Customer Support)
If you were to log back in, you'd get a fresh JWT and a new signature would be stored in the Users table. If your S/O were to try to use the site, they would not be authenticated with the their old JWT and would be forced to sign back in.
Trade-off: If we only store the JWS, or a part of it as I suggested, multiple users can't be signed in to the same account at once. How you feel should feel about that really depends on your app.
If I delete a user that has cookie based authorization, the will still have access to the system until he presses the logout button. Or until the cookie expires.
How to fix the situation.
This is a general problem of claims based authentication when removing access for users. Due to how it is designed, the database is not accessed on every request to verify the access. Instead, the cryptographically signed cookie is considered to be the source of truth. So when the access is removed in the database, the cookie is still valid and it is not trivial how to update the user’s cookie in that case. Since it is a cookie that is stored on the client side, you also can’t just log out the user remotely.
But there is a mechanism in ASP.NET Core Identity that enforces the cookie authentication scheme to re-validate an identity on a certain interval. You can configure this like this in your Startup’s ConfigureServices method:
services.Configure<SecurityStampValidatorOptions>(options =>
{
options.ValidationInterval = TimeSpan.FromMinutes(10);
});
This would set the interval to 10 minutes. So every 10 minutes, a user accessing your site with an existing cookie would be validated, and a new cookie would be issued. This process is completely silent and happens behind the scenes.
Depending on how strictly you want to enforce this, you would have to lower this interval further. I would generally recommend you to still leave it at a certain duration. Otherwise you are defeating the purpose of the cached identity.
You should also consider how problematic it really is if a user still has access to your site, and how time critical a user removal would have to be. Depending on your application, it’s also not unlikely that you retrieve the user entity within your critical actions anyway, so this would automatically fail in this case, without you having to deny access by removing the cookie.
I have a system where if the user logs in as Joe with the role "Readonly" then he will be granted access only to read things (fairly obviously) however if he logs in as Joe with the role "Administrator" then he will have access to do administrative functions. However I want him to have to relogin if he wishes to change from the Readonly role to the Administrator role so that he could potentially leave his account logged in as Readonly on a display screen or something without fear of someone hijacking his Administrator priviledges.
Now I also need to be able to log in a Web client via an implicit grant or another server via a code grant and have that service be able to use the same roles as well (while still requiring Joe to log in as the particular role if he isn't already authenticated.)
Now I have been trying to do this with IdentityServer3 but I cant seem to get the role information to be part of the authentication for the user, I tried adding an acr_value of role:ReadOnly to the token request (which then turns into an authentication request if the user is not logged in) but if they log in with the acr_value of ReadOnly and then come back to log in with the acr_value of Adminstrator it just lets them on in because they are already authenticated as the user.
Any tips on what I should be using instead of what I am doing or how I might be completely off base in this OAuth2/OpenID Connect world?
I finally figured it out so for others who might want to do the same thing here is what I did.
First you have build a custom UserService that looks in the acr_values for extra information. Then create a claim for that extra information in the AuthenticateResult.
Second you have to override the ClaimProvider to include your custom claim set in step one in the tokens generated.
Next you need a CustomRequestValidator in order to check if a new acr_value is being set compared to the one you have stored in token being currently used. If it has changed and you want to force the user to reauthenticate you can set 'request.PromptMode = "login";'
And that is it, using that set of steps I can now authenticate a user using 3 values (username, password, and role) and if the role requested changes I can require them to reauthenticate.
Works swimingly.
I am working on an ASP.NET MVC5 web app that has the following relevant properties:
Uses MVC5.1, Identity 2.0, not restricted to any legacy stuff - can use newest everything.
A user belongs to at least one organization.
Each organization has at least one user that is an organization admin.
The organization admin can allow users specific rights, such as becoming another organization admin.
From what I have understood, a good way to handle this would be to use claims.
In addition to the default claims, a user could then typically have the following claims:
Organization: {some guid}
Organization-Admin: {same guid as above}
Organization-Publisher: {same guid again}
There could potentially be super-admins, or even super-users, with claims giving them access to different roles in multiple organizations.
Now, lets imagine I am user admin logged on to my administration panel, and I want to grant user user the Organization-Publisher claim for an organization that I have the Organization-Admin claim for.
For simplicity, I have a model object that would look something like this:
public class GrantRightModel
{
public string OrganizationId { get; set; }
public string UserId { get; set; }
public string Right { get; set; }
}
and for the purposes of this example, we get the following values from our admin user:
OrganizationId: The GUID of an organization that admin has an Organization-Admin claim for.
UserId: user
Right: Organization-Publisher
There are a number of things that have evaded me about this way of doing things. Lets say we have an OrgAdmin controller with a GrantRight action;
Will the controller action need to check that the currently logged in user (found using for instance ClaimsPrincipal.Current) has the Organizaton-Admin claim for GrantRightModel.OrganizationId "manually", or are there clever auth attributes for this kind of thing?
What is the correct way in code to add the Organization-Publisher: {guid} claim to user? My controller will get a UserManager as necessary by dependency injection.
If user is signed in to the application when this happens, is there a way to have him automatically have the correct claim starting with his next request? I've seen mentioned ways to do this that would cause the user to be logged out, so that he would log in again and start a fresh session with his new claims. I see why that is the way it is, but given that the user could be working on something that takes a lot of time, it might not be popular to just have him logged out when he tries to submit his work.
There is a bit of information on this on the web, but my google-fu has failed me in finding the one that shows me how to beat these three points.
Anyone?
Thanks!
Regardins your questions.
Will the controller action need to check that the currently logged in user (found using for instance ClaimsPrincipal.Current) has the Organizaton-Admin claim for GrantRightModel.OrganizationId "manually", or are there clever auth attributes for this kind of thing?
No. The Authorize attribute takes an optional list of roles that are checked upon authorization. If one of role claims points to that role, used is authorized with no custom code involved.
What is the correct way in code to add the Organization-Publisher: {guid} claim to user? My controller will get a UserManager as necessary by dependency injection.
You create the ClaimsIdentity with proper claims and then ask the authorization manager to sign in used with the identity. The identity is typically then persisted in a cookie together with claims.
If user is signed in to the application when this happens, is there a way to have him automatically have the correct claim starting with his next request? I've seen mentioned ways to do this that would cause the user to be logged out, so that he would log in again and start a fresh session with his new claims. I see why that is the way it is, but given that the user could be working on something that takes a lot of time, it might not be popular to just have him logged out when he tries to submit his work.
I've read it twice and don't quite get it. When you issue a cookie, the cookie is responsible for retaining the authenticated session. Claims are then rebuilt early in each request pipeline based on the cookie. Numerous requests can be then issued by the user and he is still "logged in" as long as the cookie is not invalidated or the auth token stored in the cookie expires.