1
votes

I am developing a MVC application that uses OpenID and IdentityServer3.

Background:

I am running into a issue that when the Authentication Cookie times out, I need to use the refresh token to generate a new one.

I am able to login and receive the AuthorizationCodeReceived notification, which i use to request an authorization code and retrieve a RefreshToken which I store in the claims of the AuthenticationTicket.

I have tried adding logic to check and refresh the authentication in:

  • CookieAuthenticationProvider.OnValidateIdentity -- This works to refresh, and I was able to update the cookie, but it is not called after the cookie expired.
  • Adding code in the begining of the the ResourceAuthorizationManager.CheckAccessAsync -- this does not work because the identity is null and I cannot retrieve the refresh token claim.
  • Adding a filter Filter for MVC, but I am unable to figure out what to add as a HttpResponseMessage for WebAPI.

    public const string RefreshTokenKey = "refresh_token";
    public const string ExpiresAtKey = "expires_at";
    private const string AccessTokenKey = "access_token";
    
    private static bool CheckAndRefreshTokenIfRequired(ClaimsIdentity id, out ClaimsIdentity identity)
    {
        if (id == null)
        {
            identity = null;
            return false;
        }
    
        if (id.Claims.All(x => x.Type != ExpiresAtKey) || id.Claims.All(x => x.Type != RefreshTokenKey))
        {
            identity = id;
            return false;
        }
        //Check if the access token has expired
        var expiresAt = DateTime.Parse(id.FindFirstValue(ExpiresAtKey));
        if ((expiresAt - DateTime.Now.ToLocalTime()).TotalSeconds < 0)
        {
            var client = GetClient();
    
            var refreshToken = id.FindFirstValue(RefreshTokenKey);
    
            var tokenResponse = client.RequestRefreshTokenAsync(refreshToken).Result;
    
            if (tokenResponse.IsError)
            {
                throw new Exception(tokenResponse.Error);
            }
    
            var result = from c in id.Claims
                where c.Type != AccessTokenKey &&
                      c.Type != RefreshTokenKey &&
                      c.Type != ExpiresAtKey
                select c;
    
            var claims = result.ToList();
    
            claims.Add(new Claim(AccessTokenKey, tokenResponse.AccessToken));
            claims.Add(new Claim(ExpiresAtKey, DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
            claims.Add(new Claim(RefreshTokenKey, tokenResponse.RefreshToken));
    
    
            identity = new ClaimsIdentity(claims, id.AuthenticationType);
            return true;
        }
        identity = id;
        return false;
    }
    

Links:

How would I use RefreshTokenHandler?

Identity Server3 documentation Looked at the two examples, but using resourceowner flow for openid doesn't seem the right way. The MVC code flow relies on the User still having the principle, but my claims are all empty in the resource authorize.

EDIT: Okay, so if I set the AuthenticationTicket.Properties.ExpiresUtc to null in AuthorizationCodeReceived, it is setting it to null then somewhere down the line it is setting it to 30 days instead of 5 minutes (I searched the katana and identity server source code but could not find where it is setting this value), which I can live with, but would prefer it to be the same as the browser where it is "Session"

            app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            CookieManager = new SystemWebChunkingCookieManager(),

            Provider = new CookieAuthenticationProvider()
            { 
              OnValidateIdentity  = context =>
              {
                  ClaimsIdentity i;
                  if (CheckAndRefreshTokenIfRequired(context.Identity, out i))
                  {
                      context.ReplaceIdentity(i);
                  }
                  return Task.FromResult(0);
              }
            }
        });
1
Was the call to context.ReplaceIdentity() enough to replace the cookie?grudolf

1 Answers

1
votes

The problem was that in the AuthorizationCodeRecieved notification I was passing the Properties from the original ticket, which had the timeout set for Expires for the authorization code Changing the the code to pass null in resolved the issue and allowed the CookieAuthenticationHandler.ApplyResponseGrantAsync to pass its own properties.

var claimsIdentity = new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role");
n.AuthenticationTicket = new AuthenticationTicket(claimsIdentity, null);