6
votes

I've recently setup IdentityServer v3 and its running like a dream, however I'm having troubles with the OWIN middleware.

I would like to use the hybrid flow so I can refresh tokens in the backend without the user having to redirect back to the IdentityServer to get a new access token every 5 minutes (which is also odd as its set to have a lifetime of 1 hour on the server).

I'm using the following config in startup and I'm getting the tokens fine, but it never seems to try and refresh the access token once it's expired. Do I need some custom logic somewhere to refresh my tokens?

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = clientId,
            ClientSecret = clientSecret, //Not sure what this does?

            Authority = "https://auth.example.com",

            RedirectUri = "http://website.example.com",
            PostLogoutRedirectUri = "http://website.example.com",

            ResponseType = "code id_token token",
            Scope = "openid profile email write read offline_access",

            SignInAsAuthenticationType = "Cookies",

            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = async n =>
                {
                    // filter "protocol" claims
                    var claims = new List<Claim>(from c in n.AuthenticationTicket.Identity.Claims
                                                 where c.Type != "iss" &&
                                                       c.Type != "aud" &&
                                                       c.Type != "nbf" &&
                                                       c.Type != "exp" &&
                                                       c.Type != "iat" &&
                                                       c.Type != "nonce" &&
                                                       c.Type != "c_hash" &&
                                                       c.Type != "at_hash"
                                                 select c);

                    // get userinfo data
                    var userInfoClient = new UserInfoClient(
                        new Uri(n.Options.Authority + "/connect/userinfo"),
                        n.ProtocolMessage.AccessToken);

                    var userInfo = await userInfoClient.GetAsync();
                    userInfo.Claims.ToList().ForEach(ui => claims.Add(new Claim(ui.Item1, ui.Item2)));

                    // get access and refresh token
                    var tokenClient = new OAuth2Client(
                        new Uri(n.Options.Authority + "/connect/token"),
                        clientId,
                        clientSecret);

                    var response = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);

                    claims.Add(new Claim("access_token", response.AccessToken));
                    claims.Add(new Claim("expires_at", DateTime.UtcNow.AddSeconds(response.ExpiresIn).ToLocalTime().ToString(CultureInfo.InvariantCulture)));
                    claims.Add(new Claim("refresh_token", response.RefreshToken));
                    claims.Add(new Claim("id_token", n.ProtocolMessage.IdToken));

                    //Does this help?
                    n.AuthenticationTicket.Properties.AllowRefresh = true;

                    n.AuthenticationTicket = new AuthenticationTicket(
                        new ClaimsIdentity(
                            claims.Distinct(new ClaimComparer()),
                            n.AuthenticationTicket.Identity.AuthenticationType),
                        n.AuthenticationTicket.Properties);
                },

                RedirectToIdentityProvider = async n =>
                {
                    // if signing out, add the id_token_hint
                    if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
                    {
                        var id = n.OwinContext.Authentication.User.FindFirst("id_token");

                        if (id != null)
                        {
                            var idTokenHint = id.Value;
                            n.ProtocolMessage.IdTokenHint = idTokenHint;
                        }
                    }
                }
            }
        });

I'm also using the following in my ApiClient (RestSharp) which talks to my resource api

public class MyTokenAuthenticator : IAuthenticator
{
    public void Authenticate(IRestClient client, IRestRequest request)
    {
        var tokenClaim = ClaimsPrincipal.Current.Claims.FirstOrDefault(c => c.Type.Equals("access_token"));

        if (tokenClaim != null && !String.IsNullOrWhiteSpace(tokenClaim.Value))
            request.AddHeader("Authorization", String.Format("Bearer {0}", tokenClaim.Value));
    }
}
1

1 Answers

2
votes

I was able to get a refresh token and then use it to get a new access token: I followed similar logic as yours to get a token. I created the following method which I called every time I needed a token:

private static async Task CheckAndPossiblyRefreshToken(ClaimsIdentity id)
    {
        var clientName = "Myhybridclient";
        // check if the access token hasn't expired.
        if (DateTime.Now.ToLocalTime() >=
             (DateTime.Parse(id.FindFirst("expires_at").Value)))
        {
            // expired.  Get a new one.
            var tokenEndpointClient = new OAuth2Client(
                new Uri(Constants.TokenEndpoint),
                clientName,
                "secret");

            var tokenEndpointResponse =
                await tokenEndpointClient
                .RequestRefreshTokenAsync(id.FindFirst("refresh_token").Value);

            if (!tokenEndpointResponse.IsError)
            {
                // replace the claims with the new values - this means creating a 
                // new identity!                              
                var result = from claim in id.Claims
                             where claim.Type != "access_token" && claim.Type != "refresh_token" &&
                                   claim.Type != "expires_at"
                             select claim;

                var claims = result.ToList();

                claims.Add(new Claim("access_token", tokenEndpointResponse.AccessToken));
                claims.Add(new Claim("expires_at",
                             DateTime.Now.AddSeconds(tokenEndpointResponse.ExpiresIn)
                             .ToLocalTime().ToString()));
                claims.Add(new Claim("refresh_token", tokenEndpointResponse.RefreshToken));

                var newIdentity = new ClaimsIdentity(claims, "Cookies");
                var wrapper = new HttpRequestWrapper(HttpContext.Current.Request);
                wrapper.GetOwinContext().Authentication.SignIn(newIdentity);
            }
            else
            {
                // log, ...
                throw new Exception("An error has occurred");
            }
        }
    }