How to invalidate tokens after password change - c#

I am working on an API that uses JWT token auth. I've created some logic behind it to change user password with a verification code & such.
Everything works, passwords get changed. But here's the catch:
Even if the user password has changed and i get a new JWT token when authenticating...the old token still works.
Any tip on how i could refresh/invalidate tokens after a password change?
EDIT: I've got an idea on how to do it since i've heard you can't actually invalidate JWT tokens.
My idea would be to create a new user column which has something like "accessCode" and store that access code in the token. Whenever i change the password i also change accessCode (something like 6 digit random number) and i implement a check for that accessCode when doing API calls (if the accesscode used in the token doesnt match the one in the db -> return unauthorized).
Do you guys think that would be a good approach or is there some other way ?

The easiest way to revoke/invalidate is probably just to remove the token on the client and pray nobody will hijack it and abuse it.
Your approach with "accessCode" column would work but I would be worried about the performance.
The other and probably the better way would be to black-list tokens in some database. I think Redis would be the best for this as it supports timeouts via EXPIRE so you can just set it to the same value as you have in your JWT token. And when the token expires it will automatically remove.
You will need fast response time for this as you will have to check if the token is still valid (not in the black-list or different accessCode) on each request that requires authorization and that means calling your database with invalidated tokens on each request.
Refresh tokens are not the solution
Some people recommend using long-lived refresh tokens and short-lived access tokens. You can set access token to let's say expire in 10 minutes and when the password change, the token will still be valid for 10 minutes but then it will expire and you will have to use the refresh token to acquire the new access token. Personally, I'm a bit skeptical about this because refresh token can be hijacked as well: http://appetere.com/post/how-to-renew-access-tokens and then you will need a way to invalidate them as well so, in the end, you can't avoid storing them somewhere.
ASP.NET Core implementation using StackExchange.Redis
You're using ASP.NET Core so you will need to find a way how to add custom JWT validation logic to check if the token was invalidated or not. This can be done by extending default JwtSecurityTokenHandler and you should be able to call Redis from there.
In ConfigureServices add:
services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect("yourConnectionString"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.SecurityTokenValidators.Clear();
// or just pass connection multiplexer directly, it's a singleton anyway...
opt.SecurityTokenValidators.Add(new RevokableJwtSecurityTokenHandler(services.BuildServiceProvider()));
});
Create your own exception:
public class SecurityTokenRevokedException : SecurityTokenException
{
public SecurityTokenRevokedException()
{
}
public SecurityTokenRevokedException(string message) : base(message)
{
}
public SecurityTokenRevokedException(string message, Exception innerException) : base(message, innerException)
{
}
}
Extend the default handler:
public class RevokableJwtSecurityTokenHandler : JwtSecurityTokenHandler
{
private readonly IConnectionMultiplexer _redis;
public RevokableJwtSecurityTokenHandler(IServiceProvider serviceProvider)
{
_redis = serviceProvider.GetRequiredService<IConnectionMultiplexer>();
}
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
// make sure everything is valid first to avoid unnecessary calls to DB
// if it's not valid base.ValidateToken will throw an exception, we don't need to handle it because it's handled here: https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L107-L128
// we have to throw our own exception if the token is revoked, it will cause validation to fail
var claimsPrincipal = base.ValidateToken(token, validationParameters, out validatedToken);
var claim = claimsPrincipal.FindFirst(JwtRegisteredClaimNames.Jti);
if (claim != null && claim.ValueType == ClaimValueTypes.String)
{
var db = _redis.GetDatabase();
if (db.KeyExists(claim.Value)) // it's blacklisted! throw the exception
{
// there's a bunch of built-in token validation codes: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/7692d12e49a947f68a44cd3abc040d0c241376e6/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
// but none of them is suitable for this
throw LogHelper.LogExceptionMessage(new SecurityTokenRevokedException(LogHelper.FormatInvariant("The token has been revoked, securitytoken: '{0}'.", validatedToken)));
}
}
return claimsPrincipal;
}
}
Then on your password change or whatever set the key with jti of the token to invalidate it.
Limitation!: all methods in JwtSecurityTokenHandler are synchronous, this is bad if you want to have some IO-bound calls and ideally, you would use await db.KeyExistsAsync(claim.Value) there. The issue for this is tracked here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/468 unfortunately no updates for this since 2016 :(
It's funny because the function where token is validated is async: https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L107-L128
A temporary workaround would be to extend JwtBearerHandler and replace the implementation of HandleAuthenticateAsync with override without calling the base so it would call your async version of validate. And then use this logic to add it.
The most recommended and actively maintained Redis clients for C#:
StackExchange.Redis (also used on stackoverflow) (Using StackExchange.Redis in a ASP.NET Core Controller)
ServiceStack.Redis (commercial with limits)
Might help you to choose one: Difference between StackExchange.Redis and ServiceStack.Redis
StackExchange.Redis has no limitations and is under the MIT license.
So I would go with the StackExchange's one

The simplest way would be: Signing the JWT with the users current password hash which guarantees single-usage of every issued token. This is because the password hash always changes after successful password-reset.
There is no way the same token can pass verification twice. The signature check would always fail. The JWT's we issue become single-use tokens.
Source- https://www.jbspeakr.cc/howto-single-use-jwt/

The following approach brings together the best of each approach proposed previously:
Create the column "password_id" in the "user" table.
Assign a new UUID to "password_id" when creating a user.
Assign a new UUID to "password_id" every time the user changes his password.
Sign the authorization JWTs using the "password_id" of the respective user.
If more performance is needed, simply store the "password_id" of the users in Redis.
Advantages of this approach:
If a user changes his password all JWTs existing up to that moment will automatically become invalid forever.
It does not matter if a user changes his password to an old one.
It is not necessary to store the JWTs in the server side.
It is not necessary to add any extra data in the JWT payload.
The implementation using Redis is very simple.

Related

ASP.NET Core custom login setup

I have a situation where we use the ASP.NET Core Identity Framework for the Intranet system with hooks into an old CRM database (this database can't be changed without monumental efforts!).
However, we're having customers login to a separate DBContext using identity framework, with an ID to reference back to the CRM. This is in a separate web app with shared projects between them.
This is cumbersome and causes issues when customers are merged in the CRM, or additional people are added to an account etc. Plus we do not need to use roles or any advanced features for the customer login.
So I was thinking to store the username and password in the CRM with the following process:
Generate a random random password.
Use the internal database ID as the salt.
Store the Sha256 hash of the "salt + password" in the password field.
When a customer logs in, we:
Check the Sha256 hash against the salt and given password
If successful, store a session cookie with the fact the customer is logged in: _session.SetString("LoggedIn", "true");
Each request to My Account will use a ServiceFilter to check for the session cookie. If not found, redirect to the login screen.
Questions:
Is this secure enough?
Should we generate a random salt? If stored in the customer table how would it be different to the internal (20 character) customer ID?
Is there a way for the server session cookie to be spoofed? Should we store a hash in the session which we also check on each action?
Is this secure enough?
Generally roll-your-own security is a bad idea because it won't have faced as much scrutiny as an industry standard like Identity Framework. If your application is not life-or-death then maybe this is enough.
Should we generate a random salt?
Yes, salts should always be random. One reason is that when a user changes their password, back to a previous password, if the salt is constant too, then you would get the same hash again, which could be detected.
Another reason is that we don't want the salts to be predictable or sequential. That would make it easier for hackers to generate rainbow tables.
If stored in the customer table how would it be different to the
internal (20 character) customer ID?
I suppose if your customer ID is already a long random guid then that might not matter exactly, but best to play it safe, with cryptographically random disposable salts.
Look at solutions which use RNGCryptoServiceProvider to generate the salt.
Is there a way for the server session cookie to be spoofed?
I don't think a hacker could create a new session just by spoofing. They would need the username & password.
But they could highjack an existing session using Cross-Site Request Forgery.
Should we store a hash in the session which we also check on each
action?
I don't think that would help. Your _session.SetString("LoggedIn", "true") value is already stored on the server and is completely inaccessible from the client. The client only has access to the session cookie, which is just a random id. If that LoggedIn session value is true, then a hash wouldn't make it extra true.
Last year I made a custom IUserPasswordStore for a customer. This solution involved Microsoft.AspNetCore.Identity.UserManager which handles password hashing behind the scenes, no custom password handling required. You will be responsible for storing hashed password in db along with other user properties.
I cannot publish the code in its entirety, it is not my property, but I can sketch up the main parts.
First, we need the IdentityUser:
public class AppIdentityUser : IdentityUser<int>
{
public string Name { get; set; }
}
Then an implementation if IUserPasswordStore
public class UserPasswordStore : IUserPasswordStore<AppIdentityUser>
{
private readonly IUserRepo _userRepo; // your custom user repository
private readonly IdentityErrorDescriber _identityErrorDescriber;
public UserPasswordStore(IUserRepo userRepo, IdentityErrorDescriber identityErrorDescriber)
{
_userRepo = userRepo;
_identityErrorDescriber = identityErrorDescriber;
}
public Task<IdentityResult> CreateAsync(AppIdentityUser user, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
// if email exists, fail
if (_userRepo.GetByEmailAddress(user.Email) != null)
{
return Task.FromResult(IdentityResult.Failed(_identityErrorDescriber.DuplicateEmail(user.Email)));
}
// ... convert AppIdentityUser to model class
//
_userRepo.Save(userModel);
return Task.FromResult(IdentityResult.Success);
}
... implementation of the rest of IUserPasswordStore<AppIdentityUser> comes here
}
Inject this into code for identity user CRUD-operations, e.g. user management controller:
UserManager<AppIdentityUser>
Sample code for changing password (sorry for the nesting)
var result = await _userManager.RemovePasswordAsync(identityUser);
if (result.Succeeded)
{
result = await _userManager.AddPasswordAsync(identityUser, model.Password);
if (result.Succeeded)
{
var updateResult = await _userManager.UpdateAsync(identityUser);
if (updateResult.Succeeded)
{
... do something
}
}
}
Inject this into LoginController:
SignInManager<AppIdentityUser>
We also need an implementation of
IRoleStore<IdentityRole>.
If authorization is not required, leave all methods empty.
In Startup#ConfigureServices:
services.AddIdentity<AppIdentityUser, IdentityRole>().AddDefaultTokenProviders();
services.AddTransient<IUserStore<AppIdentityUser>, UserPasswordStore>();
services.AddTransient<IRoleStore<IdentityRole>, RoleStore>();
services.Configure<CookiePolicyOptions>(options => ...
services.Configure<IdentityOptions>(options => ...
services.ConfigureApplicationCookie(options => ...
In Startup#Configure:
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
See also https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-custom-storage-providers?view=aspnetcore-3.1

Identityserver4, parameter question. Authorization code flow

I'm implementing a straight out of the box solution using IDserver4(2.3) targeting .netcore 2.2 that communicates with a FHIR client by calling:
Url/api/openid/openapp?launch=12345t6u-34o4-2r2y-0646-gj6g123456t5&iss=myservice&launchOrganization=tilt
with some HL7 simulated scopes etc. The flow is okay all the way to the token endpoint serving access and id tokens using the quickstart on an IIS with certificates and all the bezels.
My problem lies in that the client requires a parameter to be passed to the external client pointing to a file or something on the server where I have some test patient data stored/or served as Json.
Any competent way to pass a parameter with the body or the header for example? And do you do it at the authorization or the authentication, or along with the tokens? Lets call it context. The service shut me down when i reach it. Says this on their side 'TypeError: Parameter "url" must be a string, not undefined'
Thanks in advance.
Got it using:
public class CustomClaimInjection : ICustomTokenRequestValidator
{
private readonly HttpContext _httpContext;
public CustomClaimInjection(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
public Task ValidateAsync(CustomTokenRequestValidationContext context)
{
var client = context.Result.ValidatedRequest.Client;
//client.Claims.Add(new Claim("sub", sub)); // this will be [client_sub]
context.Result.CustomResponse = new Dictionary<string, object>
{
{"example-launchcontext", "https://url/" }
};
return Task.CompletedTask;
//return Task.FromResult(0);
}
}
I think I understand your problem now, and I think you would like a successful authentication to return additional information about where the patient's file is stored. I would store this in the token as a claim since it can be expressed as a statement about the subject (the user). This can be done in the registered (through dependency injection) implementation of the IProfileService. In the implementation of 'GetProfileDataAsync' you can set the issued claims using the 'ProfileDataRequestContext' parameter's property 'IssuedClaims'. These claims will be used to populate the id token which is what you should be looking to do.

Secure WebApi concept

How about this system. I need some comments and maybe critical security part for this.
System which I use is maybe little bit complicated but 100% custom and should be good. This is a system for custom authentication in sending request to Asp.NET
WebApi
System works with sending 2 request
Everything what you need is 2 pairs of data. 1st one is public and 2nd one is secret.
Second pair of data be must be known to both sides (sender and receiver)
public: ApiKey and RequstID where ApiKey is "normal" and requstID have to be unique always;
secret: UserName and Password (both side have to know these data)
Sender:
Send 1st request with 3 parameters: 1st= ApiKey, 2nd=RequstID, 3rd=Hash(ApiKey+RequestID+USerName+Pass)
Server:
Read RequstID
Read ApiKey and get data about users UserName and Pass for this ApiKey
From the own side: Hash(ApiKey+RequestID+USerName+Pass)
Check is Hash from Sender same us from Server
If is False:
BadRequest - or whatever...
if is True
Before all - Create on database on table for collect data about request.
This is table with columns (e.g.):
ID(autoincrement), RequstID, Token, TokenValidateDateTime
Before create new row, check is there already this RequestID and if there is return BadRequest.
If there is not - make new row.
RequstID is RequstID from request;
Token - Generate token (e.g. Guid.NewGuid().ToString());
TokenValidateDateTime= DateTime.Now.AddMinutes(2) - or some other value ...
In response for the first request send back this this Token (from item 2)
In the second request, Sender have to use AGAIN same RequstID and Token (from response before)
Server will check
Combination RequstID and Token
Token validation (depend on current date time);
Is everything is OK, user is validated
if is not - BadRequest, or whatever
Any suggestions or comments are welcome :)
It sounds similar to traditional website username / password authentication which returns a session cookie.
But you're also including a api key & request id and a hash. The hash won't add to much value unless there's a shared salt, as once someone works out your hashing technique it will be vulnerable to dictionary attacks.
Also generating a Guid token isn't "cryptographically secure", it is designed to be unique but it's often based upon the system clock meaning it is predictable.
Building bespoke security mechanisms are generally unadvised; doing bespoke encryption is defiantly a "no no" (which you aren't doing as far as I'm aware). Bespoke authentication is probably less risky, but seeing as there are many frameworks already existing that have been critiqued by security experts I'd suggest researching if any of those suit first.
I'd recommend looking at asp.net core's security options: https://learn.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-2.1

Redirect after cookie validation fails in ASP.NET Core

I have implemented a cookie validator as described in the ASP.NET docs:
public static class CookieValidator
{
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
...
if (invalidCookie)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync("MyCookieScheme");
}
}
}
It seems to be working correctly and gets into the invalidCookie block, rejecting the principal and signing out. After that I would like to redirect to a different URL. How do I have it redirect if I invalidate the cookie?
It should already return a 302 and add a 'location' header to your response with either the 'AccessDeniedPath' or 'LoginPath' when you reject principal, however lets say you want to add a header change the status code, or other customization.
In the invalidCookie block you can set custom headers with the following.
context.httpContext.Response.Headers["forceRedirect"] = httpContext.Request.Host.ToString();
Then when you get the response back on the client you need to check if this header is present, if it is then you redirect to the link given. This allows you to at least set headers, however if you changed the status code in the same place the framework will still override the status code when you reject principal (after you are out of the ValidateAsync function).
In order to do this, and stop the framework from overriding your custom response you have to declare an onRedirectToAccessDenied funtion.
In startup.cs: ConfigureServices
services.ConfigureApplicationCookie(options => { //this just makes checks against the cookie, so if the user deletes the cookie then ValidateAsync never fires
// Cookie settings ...
options.Events.OnRedirectToAccessDenied = CookieValidator.overrideRedirect;
options.Events.OnValidatePrincipal = CookieValidator.ValidateAsync;
});
Then in CookieValidator:
It is worth noting that if you do this then other rejections will execute this code too. Like if you also have a Roles authorization then when the user fails that authorization it will call this. This can be frustrating if you want the code to return different responses based upon why auth failedI am solving this by using policies and having them return the custom responses because it knows exactly why it failed then have overrideRedirect empty so the framework doesn't change my response.
internal static async Task overrideRedirect(RedirectContext<CookieAuthenticationOptions> context) {
...
//In here you can customize the response anyway you want and it will not be changed
}
It should be noted that ValidateAsync only is called if the cookie is present in the request, so if the user deletes the cookie then they can skip this validation. In my case I got around this by validating the cookie here, but still had other auth code (i added policy requirements in an IAuthorizationHandler) later that verified that the user had a claim. If the user deleted their cookie then the claim wouldn't be there and they would still lose access.

Understanding storage of OAuth token

I have implemented ASP.NET Identity authentication and OAuth authorization according to this tutorial: http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
It's currently working but i don't fully understand where the TOKEN and it's timer is stored.
This is the code that generates token:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
I would guess that token is stored in the ASP.NET Identity DB or within the hosted WEB API Application, but i don't fully understand.
the token is only generated once by the provider and it is not stored anywhere. It contains the information the application needs to authenticate the request and nothing more.
Assuming you use the Json Web Tokens or JWT, then the token is nothing more than a Json object with some properties, such as when it expires, the actual duration in seconds etc.
The token last for a configurable duration, so assuming you want to reuse that token for multiple calls then the client application will need to store somewhere in a safe manner. It could be in session for example, you could store the whole token and when you need it simply check if it's still active by looking at the duration. If it's not active anymore, you either refresh the current one you have or simply request another.
You could encapsulate all this nicely with something like this :
private TokenModel GetToken()
{
TokenModel result = null;
if (this._systemState.HasValidToken(this._currentDateTime) )
{
result = this._systemState.RetrieveUserData().TokenData;
}
else
{
try
{
result = this._portalApiWrapperBase.RequestAccessTokenData();
}
catch(Exception ex)
{
this.LastErrorMessage = ex.Message;
}
finally
{
this._systemState.AddTokenData(result);
}
}
return result;
}
In my case all this user data is stored in Session and simply retrieved from there.
I am not showing all the code here, but I basically have a state provider where I store the token once I receive it the first time. The next time I need it, if it's still valid, I return it, or go and request another if it's not. All this is hidden from the app, you just call the GetToken method and it deals with everything else.
Now, the token is supposed to be application level, it's generated based on a ClientID and CLientSecret so you could easily request another one when you need to.
The token isn't stored. The user requesting the token needs to be able to pass the token on every request in order to make an authenticated call. So it's the responsibility of the client to store the token in order to do that. (that might be in-memory for short lived sessions or on disk/in a database for longer lived sessions.
There is no need for the server to store the token, since it is passed by the client on each request. One might store it in a db themselves on the server and check if the token is there. Using that kind of mechanism allows you to revoke a token by removing it from the db. There are other ways to do that though.
By timer I guess you mean the lifetime of the token. That is checked by the framework on every request. So there is no actual timer.

Categories