Why is the refresh-token expiration not renewed in IdentityServer persistedgrants? - c#

I'm using .NET Core and IdentityServer4 for creating access-tokens. Those access tokens also include a refresh-token, which can be used to request new access-tokens. Since some users reported, that they get logged out too early I made a test in my local environment. I set the access-token-lifetime to 10 seconds, and the refresh-token lifetime to 30 seconds. The expected behaviour is, that you keep being logged in as long as you request a new token below 30 seconds. I then sent a request every few seconds. Every 10 seconds a new access-token is requested by the refresh-token. For this I use the Extension method "RequestRefreshTokenAsync". But this only works at max. 3 times:
04.12.2020 15:09:02 Using refresh token iCmtHJa5jllqTkwUexI9ZMLGN0RQhqvXljun6AgR31M.
04.12.2020 15:09:02 Success, new refresh token T0R8BWfNwkBzBh_yDKzhqerzgRgxY9OZ3jWLG951-hc.
04.12.2020 15:09:13 Using refresh token T0R8BWfNwkBzBh_yDKzhqerzgRgxY9OZ3jWLG951-hc.
04.12.2020 15:09:13 Success, new refresh token W29SlwHP318d4NFaqRS5ZTJAUG-ugYMBQrV6-g6v1rk.
04.12.2020 15:09:23 Using refresh token W29SlwHP318d4NFaqRS5ZTJAUG-ugYMBQrV6-g6v1rk.
04.12.2020 15:09:23 Error: invalid_grant
After I discovered this I also looked at the persistedgrants table of IdentityServer and it seems that for every refreshed access-token the same CreationTime and Expiration date is stored:
ocDMiwVpOJrcWh5LmEPgzDnn15TED5FpxSEpXsXDMzY= 2020-12-04 15:08:51.000000 2020-12-04 15:09:21.000000
i+NjVCnMUoobpnaINoLrCGcQrWHloIaAHC4NHWwGyK4= 2020-12-04 15:08:51.000000 2020-12-04 15:09:21.000000
d9HjtUszofxOEcnV7w95dhn54rl8QI2IRq8UMZlKr1Y= 2020-12-04 15:08:51.000000 2020-12-04 15:09:21.000000
What I would expect is, that when I request a new access-token, also the obviously new refresh-token gets a new expiration-date.
Does anybody know if I'm missing a specific setting or how to solve this issue?

What I have done to solve this is set your refresh token to have a sliding expiration. If you request a new access token before the sliding expiration then the refresh token expiration will be extended. This should be configurable in your identity server client settings. Check the docs here: http://docs.identityserver.io/en/release/topics/refresh_tokens.html

Related

IdentityServer4 Refresh Token: How to determine expiration time?

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.
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
});
}
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.

How to handle expiration of refresh token

Hi I'm fiddling around Identity server 4. I went through some vids on plural sight and the instructor went through some code on how to refresh my access token using the refresh token.
My question is what happens when the refresh token expires? I tried to see what will happen by setting AbsoluteRefreshTokenLifetime=15 but I just get an error when the time elapses i.e. when I try to get data from the resource server. I would expect a redirect to the login page
What is the correct way to handle an expired refresh token? If the correct way is to force the user to login then please provide the code to do this as ids4 does not do this by default. I cannot seem to get it to work.
new Client
{
ClientId = "mvc2",
ClientName = "MVC Client2",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5001/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5001/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true,
RequireConsent = false,
AccessTokenLifetime = 15,
AbsoluteRefreshTokenLifetime = 15,
IdentityTokenLifetime = 15
}
The scope of the refresh token is offline_access. This means that the client can request access tokens on behalf of the user without having to interact with the user.
Depending on the flow, when the user logs in, the client will receive three tokens: the access token, the identity token and the refresh token.
Being an automated (offline) process, there is no login page. There is no active user, so the return is simply Unauthorized. Meaning that once expired the user has to login again to start the proces again.
It is possible that the user can still access the website (using the cookies) while the api is now unaccessable. You can simulate this with the hybrid flow sample.
Using the configuration of IdentityServer you have several options. You can choose sliding expiration, meaning that the refresh token 'never expires'. And you can set it to one time only use.
I believe that by default the token is set to one time use only, which will trigger IdentityServer to add a new refresh token each time a new access token is requested. Setting the fixed expiration will make sure that the user must login every now and then.
In terms of lifetime:
access token < refresh token <= AbsoluteRefreshTokenLifetime.
Just make sure the refresh token doesn't expire and isn't lost.
If you have a scenario where the refresh token was lost or expired, then logout the user. Go to a secured page which will redirect the user to the login page.
This depends on your configuration, but you could do something like this to logout the user:
await HttpContext.SignOutAsync("Cookies");
return LocalRedirect($"/secured");
In that case the user may not have to login as the user can still be active because of the SSO cookie.
If you want a full logout (all websites):
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");

ADFS TokenLifeTime Maximum?

I am using ADFS 2.0 for authentication for my mvc 3.0 web app. I set my TokenLifeTime on my relying party to 1440 (24 hours), but when I step through my code after I log in I can see that the ValidTo date of the session token is only 600 mins (10 hours) from now. If I change TokenLifeTime to be less than 600 the datetime matches what I expect when I log in. i.e. if I set TokenLifeTime to 5, the ValidTo date on my session token is 5 mins from when I logged in.
I haven't found any reference to a maximum number for this value, but I also haven't been able to account for why I can't increase the ValidTo time on my session token to longer than 600 mins.
So...
Is 600 the maximum value for TokenLifeTime?
Is there anything else that affects the ValidTo time on the session tokens issued by ADFS?
I've been looking at this and I think I've come up with a working solution - I've not used it in anger yet so I can't be sure that it doesn't contain any issues!
Essentially it intercepts the token after it has been created but before anything has started using it. Then replaces it with a token that contains all the underlying detail of the original but with a much longer validTo date, as decided by the value of validForDays
void WSFederationAuthenticationModule_SessionSecurityTokenCreated(object sender, SessionSecurityTokenCreatedEventArgs e)
{
var currentToken = e.SessionToken;
var validForDays = 1;
e.SessionToken = new SessionSecurityToken(
currentToken.ClaimsPrincipal,
currentToken.Context,
currentToken.EndpointId,
DateTime.UtcNow,
DateTime.UtcNow.AddDays(validForDays));
e.SessionToken.IsPersistent = true;
}
This lives in Global.asax.cs

Handle facebook expiration token

I am having some trouble while integrating facebook API with my desktop application.
Now, I`m trying to use Facebook SDK for .NET: http://facebooksdk.net/
Here is the problem:
When I use my tokens, Facebook returns the following message:
"Error validating access token: Session has expired at unix time 1365165488. The current unix time is 1378218499."
I saw in some posts that I can do something like this to renew my access token:
Dictionary<string, object> fbParams = new Dictionary<string, object> ();
fbParams["client_id"] = token.appId;
fbParams["grant_type"] = "fb_exchange_token";
fbParams["client_secret"] = token.appSecret;
fbParams["fb_exchange_token"] = token.accessToken;
JsonObject publishedResponse = fbClient.Get ("/oauth/access_token", fbParams) as JsonObject;
return publishedResponse["access_token"].ToString ();
But it doesn't work; it throws another exception with the same message ("Session has expired...")
Is there an easy way to do this?
I don't know if this can impact my application but this access token is like 1 year old (and I'm using in a desktop application).
Thank you very much! =)
The message is clear, you are using a token that has already expired (It's actually over four months old). You need to go back through the login flow with a user arriving at an authorization screen.
You cannot extend a token that has already expired, you can only extend tokens that are valid. e.g. a short lived (two hours) token to a long lived token (two months)

How to set the timeout properly when federating with the ADFS 2.0

I am using ADFS 2.0 for quite some time and I understand how things work. I've done dozen of custom RPs, custom STSes as well as using the ADFS as the relying STS.
However, I have a simple requirement which I still fail to fulfill.
I want my users to be forced to relogin after some fixed time. Let's say 1 minute, for test purposes.
First, I've made some corrections at the RPs side. It seems that for unknown reason, the RP retains the session even if the token's validTo points back in time. This contradicts what Vittorio Bertocci says in his book (page 123) where he shows how to perform sliding expiration - he says that "The SessionAuthenticationModule will take care of handling the expired session right after". Well, for me it doesn't, however I have found a trick here http://blogs.planbsoftware.co.nz/?p=521 - take a look at the "if" clause:
sam.SessionSecurityTokenReceived +=
( s, e ) =>
{
SessionAuthenticationModule _sam = s as SessionAuthenticationModule;
DateTime now = DateTime.UtcNow;
DateTime validFrom = e.SessionToken.ValidFrom;
DateTime validTo = e.SessionToken.ValidTo;
try
{
double halfSpan = ( validTo - validFrom ).TotalSeconds / 2;
if ( validTo < now )
{
_sam.DeleteSessionTokenCookie();
e.Cancel = true;
}
}
catch ( Exception ex )
{
int v = 0;
}
};
This trick fixes the issue at the RPs side. When the token is invalid the application clears it out and redirects to the login page.
Now comes the problem. My login page uses the FederatedPassiveSignIn control. When clicked, it redirects the browser to the ADFS.
But ADFS happily creates a new session without any prompt for the user.
I have set the token's lifetime for this RP to 1:
Set-ADFSRelyingPartyTrust -Targetname "myrpname" -TokenLifetime 1
and using Get-ADFSRelyingPartyTrust I can see that it's set to 1 (I even print the token validTo on my page to confirm that this is set correctly).
Then I set ADFS properties with ADFS-SetProperties:
ADFS-SetProperties -SsoLifetime 1
ADFS-SetProperties -ReplyCacheExpirationInterval 1
ADFS-SetProperties -SamlMessageDeliveryWindow 1
but still no luck. I am stuck now.
The scenario works correctly with my custom STS where the validity of the STS session is based on a Forms cookie - if I set the STS's forms cookie timeout to 1, after 1 minute of inactivity within my RP application I am redirected to the login page of my RP which then redirects to the STS which presents its login page.
However, this is not the case with ADFS 2.0. After a minute of inactivity, I am redirected to the login page of my RP which redirects to ADFS's login page which in turn redirects back happily just like the session would be still active within ADFS.
I would like someone to:
(1) take a look at the hack described at the top and explain why an expired token is not automatically rejected and such ugly hack is needed
(2) explain how to properly timeout the session at the ADFS 2.0 side so a request to renew the token is guarded with a login page.
Thanks in advance.
edit
I can confirm that setting all above parameters to 1 minute makes the ADFS session invalid after 5 minutes (or more). That's strage and it seems that either I am making a basic mistake or 5 minutes is the minumum acceptable value.
My (2) from above is now then just to confirm and explain my observation.
As per comments above (joint effort with the OP) the Freshness property on the FederatedPassiveSignIn instance should be set to 0.
According to http://docs.oasis-open.org/wsfed/federation/v1.2/ws-federation.html this indicates for the IP/STS to re-prompt the user for authentication before it issues the token.
You could also try changing ADFS from windows integrated authentication to forms based authentication. You will probably still have to monkey with the freshness property but now your users will have to enter their credentials even if they are on the same network as your AD.
This article explains it pretty simply:
http://social.technet.microsoft.com/wiki/contents/articles/1600.aspx
It's quite strange that setting the TokenLifetime value didn't work . The article in MSDN explains timeout as a straight forward setting - by assigning TokenLifetime value. I'm interested to know whether the setting described in MSDN is correct. If that didn't help, then it's right time to revise that article. Hope that will be a big help to those who are facing this issue.

Categories