We use ASP.NET Identity in a Web Api project with SimpleAuthorizationServerProvider
, we use OAuth-tokens to authorize each request coming from the client. (Tokens have and expire timespan, we don't use refresh tokens.)
When users change their password, I would like to invalidate the tokens they may have, possibly on other devices. Is there any way to explicitly do that? I experimented and saw that the existing tokens work without any problem after a password change, which should be prevented.
I thought about putting the password hash, or part of the hash in the OAuth token as a claim, and validating that in the OnAuthorization
method of our derived AuthorizeAttribute filter.
Would this be a correct way to solve the problem?
I do not recommend putting the hash of the password as claim, and I believe there is no direct way to invalidate token when password is changed.
But if you are Ok with hitting the DB with each request send from the client app to a protected API end point, then you need to store Token Identifier (Guid maybe) for each token granted to the resource owner requested it. Then you assign the token Identifier as a custom claim for this token, after this you need to check this table with each request by looking for the token identifier and the user name for the resource owner.
Once the password is changed you delete this token identifier record for this resource owner (user) and the next time the token sent from the client it will get rejected because the record for this token identifier and resource owner has been deleted.
I've based my approach on Taiseer's suggestion. The gist of the solution is the following. Every time a user changes their password (and when registers), a new GUID is generated and saved in the database in the User table. I call this GUID the password stamp, and store it in a property called LatestPasswordStamp
.
This stamp has to be sent down to the client as part of the token as a claim. This can be achieved with the following code in the GrantResourceOwnerCredentials
method of the OAuthAuthorizationServerProvider
-implementation.
identity.AddClaim( new Claim( "PasswordTokenClaim", user.LatestPasswordStamp.ToString() ) );
This stamp is going to be sent from the client to the server in every request, and it is verified that the stamp has not been changed in the database. If it was, it means that the user changed their password, possibly from another device. The verification is done in our custom authorization filter like this.
public class AuthorizeAndCheckStampAttribute : AuthorizeAttribute
{
public override void OnAuthorization( HttpActionContext actionContext )
{
var claimsIdentity = actionContext.RequestContext.Principal.Identity as ClaimsIdentity;
if( claimsIdentity == null )
{
this.HandleUnauthorizedRequest( actionContext );
}
// Check if the password has been changed. If it was, this token should be not accepted any more.
// We generate a GUID stamp upon registration and every password change, and put it in every token issued.
var passwordTokenClaim = claimsIdentity.Claims.FirstOrDefault( c => c.Type == "PasswordTokenClaim" );
if( passwordTokenClaim == null )
{
// There was no stamp in the token.
this.HandleUnauthorizedRequest( actionContext );
}
else
{
MyContext ctx = (MyContext)System.Web.Mvc.DependencyResolver.Current.GetService( typeof( MyContext ) );
var userName = claimsIdentity.Claims.First( c => c.Type == ClaimTypes.Name ).Value;
if( ctx.Users.First( u => u.UserName == userName ).LatestPasswordStamp.ToString() != passwordTokenClaim.Value )
{
// The stamp has been changed in the DB.
this.HandleUnauthorizedRequest( actionContext );
}
}
base.OnAuthorization( actionContext );
}
}
This way the client gets an authorization error if it tries to authorize itself with a token which was issued before the password has been changed.