6

I am working with the Identity Server 4 sample code. In particular, for the client I am using the sample MVC Client with the Hybrid flow: https://github.com/IdentityServer/IdentityServer4/tree/master/samples/Clients/src/MvcHybrid

And for the server I am using Identity Server with in-memory clients (no Entity Framework, and no ASP.Net Identity): https://github.com/IdentityServer/IdentityServer4/tree/master/samples/Quickstarts

Both client and server have pretty much vanilla, out-of-the-box configuration.

I am trying to understand how refresh tokens expire and how a native app can pro-actively determine the expiration time (before it gets rejected by an API). My understanding is that the default expiration for refresh tokens is long:

http://docs.identityserver.io/en/latest/topics/refresh_tokens.html:

Maximum lifetime of a refresh token in seconds. Defaults to 2592000 seconds / 30 days

However, when the sample code requests a refresh token, I do not get the expected expiration time. Here is the sample code:

var disco = await _discoveryCache.GetAsync();
if (disco.IsError) throw new Exception(disco.Error);

var rt = await HttpContext.GetTokenAsync("refresh_token");
var tokenClient = _httpClientFactory.CreateClient();

var tokenResult = await tokenClient.RequestRefreshTokenAsync(new RefreshTokenRequest
{
    Address = disco.TokenEndpoint,

    ClientId = "mvc.hybrid",
    ClientSecret = "secret",
    RefreshToken = rt
});

tokenResult.ExpiresIn is 3600 seconds, which is actually the expiration of an access token. I was expecting that to be 2592000 seconds. So question #1 is: Why is this the case?

But more importantly, I know that the expiration for the refresh token is in fact the default 30 days when I use SQL Server as the data store. There is a table PersistedGrants that contains the refresh tokens, and the expiration is clearly 30 days from the issue date. So question #2 is: How can an app programmatically determine the expiration date of the refresh token it received?

I've tried to parse the RefreshToken itself, but it is not really a full JWT, so this throws an error:

var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessTokenResponse.RefreshToken);
var diff = jwt.ValidTo - jwt.ValidFrom;

I've also searched through the IdentityServer4 unit / integration tests and cannot find an example of introspecting a refresh token.

Presumably that information either needs to be somewhere in the initial token response, or there needs to be an endpoint built into Identity Server. But I can't find either of these things.

anon
  • 4,578
  • 3
  • 35
  • 54
  • The expiration is from the access token because you are requesting an _access token_. The _refresh token_ only acts as a key. In case you've configured the refresh token for one-tim-use only, a new refresh token is returned as well, revoking the current refresh token. I suspect you can read the expiration time with something like: `new JwtSecurityTokenHandler().ReadJwtToken(tokenResult.RefreshToken).ValidTo;` –  Dec 16 '19 at 18:11
  • @RuardvanElburg - Thanks for the reply. Unfortunately, I had already tried using `JwtSecurityTokenHandler` to try to calculate a `TimeSpan`. But as you say, the refresh "token" does not appear to be a full JWT, so the Read method fails. I will add this to the main post. – anon Dec 17 '19 at 15:50
  • 1
    I'm trying to solve the same situation than you. The strategy we took is that, if for any reason, requesting a new access token using the refresh token fails, we request our users to log in again. In practice, this has worked fine for us. The session cookie and the access token both have a much smaller expiration time than the refresh token. So, whichever expires first, ends up requesting a new refresh token. We consider the refresh token expiration as an exceptional scenario. – mdarefull Apr 18 '20 at 13:14

2 Answers2

2

Ok, so the answer is that there is no data in the access_token response that indicates the expiration time of the refresh_token. Additionally, there is no endpoint that can be used to check the expiration.

The OAuth spec does not say anything about this, so I did not want to alter the access_token response. I wound up making my own endpoint that returns the expiration time if needed. Here is my controller action, if anyone needs a starting point:

private readonly IRefreshTokenStore _refreshTokenStore; // inject this into your controller

...

[Route("[controller]/GetRefreshTokenExpiration")]
[Authorize(...YOUR SCOPE...)]
public async Task<IActionResult> GetRefreshTokenExpiration(string refreshTokenKey)
{
    var refreshToken = await this._refreshTokenStore.GetRefreshTokenAsync(refreshTokenKey);
    if (refreshToken == null)
    {
        return NotFound(new { message = "Refresh token not found" });
    }
    return Ok(new {
        message = "Refresh token found",
        lifetime_seconds = refreshToken.Lifetime
    });
}
anon
  • 4,578
  • 3
  • 35
  • 54
  • 3
    You would better use async/await while calling GetRefreshTokenAsync(), rather than .Result. – ageroh Feb 05 '21 at 20:26
0

When one calls ../token We get access_token, expires_in, refresh_expires_in, refresh_token and other stuff

Decode access_token to get ValidTo substract expires_in from ValidTo and then add refresh_expires_in to ValidTo and that should give you the expiry date of the refresh_token.

Jason
  • 21
  • 1