[Question]How to validate SecurityStamp on Client
I'm using the CookieAuthenticationMiddleware and now trying to integrate IdentityServer3 in combination with IdentityServer3.AspNetIdentity. To invalidate cookies when user information (password, external accounts, ...) changes you would normally use the SecurityStampValidator in the OnValidateIdentity callback.
With IdentityServer3.AspNetIdentity the security stamp validation is now moved to IsActiveAsync of the AspNetIdentityUserService class.
Please correct me if I'm wrong, but my understanding is that I now have to call one of the IdentityServer3 endpoints within the OnValidateIdentity callback to trigger IsActiveAsync and validate the result?
This would be either the Identity Token Validation endpoint (if I only store the identity token) or the UserInfo / Token Introspection endpoint (where I would also have to store the access token and add a resource scope) - all of them would trigger IsActiveAsync and reflect this information into the response?
Either way, IsActiveAsync would always return true, because the security_stamp is not included in the claims and AspNetIdentityUserService:IsActiveAsync would set ctx.IsActive to true for this cases.
- Shouldn't
IsActiveAsyncsetctx.IsActiveto false ifEnableSecurityStampanduserManager.SupportsUserSecurityStampare set to true but no security stamp is available? - As far as I understand to make security stamp validation work on the client side I have to include the security stamp in the identity or access token (depending on which endpoint I use for validation) so
IsActiveAsynccan validate it? - If this is correct, which concrete combination would you suggest? A custom resource scope with security stamp as claim?
You'd have to build this custom,.
Thanks for your response. Any comments / suggestions for the implementation or some answers to my questions?
I have a more or less working sample, but I'm still open for any feedback.
I'm storing the security_stamp in the identity token:
new Scope()
{
Name = "security_stamp",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>() { new ScopeClaim("security_stamp") }
}
OnValidateIdentity mimics more or less what SecurityStampValidator.cs does.
OnValidateIdentity = async context =>
{
// Remote validation of security stamp
// Adapted version of: http://www.symbolsource.org/MyGet/Metadata/aspnetwebstacknightly/Project/Microsoft.AspNet.Identity.Owin/2.0.0-beta1-140130/Release/Default/Microsoft.AspNet.Identity.Owin/Microsoft.AspNet.Identity.Owin/SecurityStampValidator.cs?ImageName=Microsoft.AspNet.Identity.Owin
DateTimeOffset currentUtc = DateTimeOffset.UtcNow;
if (context.Options != null && context.Options.SystemClock != null)
{
currentUtc = context.Options.SystemClock.UtcNow;
}
DateTimeOffset? issuedUtc = context.Properties.IssuedUtc;
// Only validate if enough time has elapsed
bool validate = issuedUtc == null;
if (issuedUtc != null)
{
TimeSpan timeElapsed = currentUtc.Subtract(issuedUtc.Value);
validate = timeElapsed > TimeSpan.FromMinutes(5);
}
if (validate)
{
// Check via identity token validation (http://identityserver.github.io/Documentation/docsv2/endpoints/identityTokenValidation.html)
// Calls IsActiveAsync of IUserService and compares local security_stamp with remote security_stamp
HttpClient client = new HttpClient();
string idToken = context.Identity.Claims.Where(x => x.Type == "id_token").Select(x => x.Value).SingleOrDefault();
if (string.IsNullOrEmpty(idToken))
{
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return;
}
HttpResponseMessage response =
await
client.GetAsync(string.Format("{0}/connect/identitytokenvalidation?token={1}&client_id={2}",
Authentication.Configuration.IdentityManagerAuthority, idToken, "webapplication"));
if (!response.IsSuccessStatusCode)
{
// Reject identity, force reauthentication
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return;
}
context.OwinContext.Authentication.SignIn(
new AuthenticationProperties { IsPersistent = context.Properties.IsPersistent }, context.Identity);
}
}
I'm still not sure if I'm using the "right" scope / validation endpoint and it only works as long as the identity token lifetime is not expired, which is kind of a drawback.
Putting info in the id_token (I don't think) will help -- it's a one-time thing at a point in time. At some point in the future the password changes, so the security stamp changes, and there's no connection back to the server/db that this has happened. Like I said, it'd have to be custom and an explicit connection/call back to the Id management system.
and there's no connection back to the server/db that this has happened.
That's why I would call one of the IdentityServer endpoints within OnValidateIdentity, which triggers AspNetIdentityUserService:IsActiveAsync, which verifies the security stamp in the token (like in the code posted above)?
That's why I would call one of the IdentityServer endpoints within OnValidateIdentity, which triggers AspNetIdentityUserService:IsActiveAsync, which verifies the security stamp in the token (like in the code posted above)?
Ok, so now I finally got the whole picture of what you're doing. So yes, this sounds like it's fine. I'd just make a custom endpoint for the check (not sure how you're doing that part now).
Why would you make a custom endpoint? At the moment I'm using on of the existing endpoints, because they check IsActiveAsync anyway:
HttpResponseMessage response =
await
client.GetAsync(string.Format("{0}/connect/identitytokenvalidation?token={1}&client_id={2}",
Authentication.Configuration.IdentityManagerAuthority, idToken, "webapplication")
Because identity token validation is validating the id_token (including its expiration), which isn't quite what you needed to check, right? You needed to know if the security stamp has changed. Those are different semantics.
That is true, but it was the best I came up with so far. If possible, I would like to have an endpoint which checks the IsUserActive function and returns true / false.
How would you suggest to implement this custom endpoint? I scanned over the code and samples and I think it's not possible to integrate it directly into Identity Server? So I guess the best way would be creating a custom middleware, passing the IUserService (or the factory) and instantiate an IsActiveContext and call IsUserActive for the validation?
Sure, a custom middleware after idsvr that uses the OWIN extensions to resolve dependencies from IdSvr's DI system.
@Dresel Did you ever get this working? I am facing a similar challenge right now and would love it if you had a working sample!
Sorry for my late reply, I was partly on vacation and also had other things to do.
I dug a little bit into the IdentityServer code base, this is the solution I came up with:
On the IdentityServer side:
- Add security stamp to the Clients and Scope configuration
- Override
AspNetIdentityUserServiceto allow retrieval of security stamp as claim - Implement a custom OWIN middleware that expects a clientID and (at least) the sub and security stamp claim (I guess the easiest way to pass this information is to send the identity token?)
- This middleware then creates an
IsActiveContextinstance (Client is accessed viaIClientStoreand clientID, Subject can be created viaPrincipal.Createand the list of claims) - This context can be passed to the
IsActiveAsyncfunction of theAspNetIdentityUserService, which validates security stamp if present as claim - The result of
IsActiveAsyncis returned by the OWIN middleware
- This middleware then creates an
On the client (MVC) side:
- Add the security stamp as scope to the
OpenIdConnectAuthenticationOptions - Mimic the implementation of the SecurityStampValidator
- If revalidation is needed, call the custom middleware on the IdentityServer side to check
IsActiveContext
- If revalidation is needed, call the custom middleware on the IdentityServer side to check
@brockallen Is that what you had in mind? Would you pass the needed information as identity token to the custom middleware or would you use a custom format for clientID and the list of claims?
@mikesigs I don't mind posting a working sample as soon as I have decided on all the details. If there's interest I could also send an PR for the IdentityServer3.Samples repository.
Can I ask why not just have the cookie expiry set by the id_token lifetime instead? OpenIdConnectAuthenticationOptions's UseTokenLifetime value is true by default. Once the expiry time is reached a subsequent request would trigger the OpenId middleware to authenticate again and thereby do all the IsActive and SecurtyStamp checks.
If the token time is kept short then cookie replay is kept to a minimum. The down-side is if your app requires consent each login they will be presented a consent screen each time but if that's not your use case then maybe this is an option.
I'm not sure if I understood you correctly.
As far as I understand, as long as the Cookie of the UseCookieAuthentication middleware is valid, there will be no SecurityStamp checks. In our current setup, if the CookieMiddleware cookie is expired, the User is redirected to the Login Page of our ASP.NET project. There he can choose one external provider which redirects to IdentityServer which then redirects back to the ASP.NET project.
That's why we are using the default CookieMiddleware lifetime (14 Days) in combination with custom security stamp validation to react on privilege changes (e.g. roles added / removed).
Sorry, I had assumed your login workflow was hosted by IS3. In my assumed scenario if the cookie has expired or rejected by the cookie middleware and the user hasn't explicitly logged out of the app/IS3 they will transparently be authenticated again.