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
Related
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
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.
I'm trying to set a Cookie to the browser from back-end (Asp.Net core) which should expire on the next day same time minus 5 minutes. Here is the C# code from controller
HttpContext.Response.Cookies.Append("MyCookie",
"test cookie value",
new Microsoft.AspNetCore.Http.CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddDays(1).AddMinutes(-5)
});
But to the browser it is coming with wrong expiration DateTime.
For example if cookie expiration date was set to 2016-09-28 19:15, on the browser it will expire at 2016-09-29T17:15, and it is 2 hours less, which is weird because my time zone is +1.
DateTimeOffset.UtcNow is DateTimeOffset.Now + yourTimezone.
So
DateTimeOffset.UtcNow.AddDays(1).AddMinutes(-5)
Will return the same as
DateTimeOffset.Now.AddDays(1).AddMinutes(-5).AddHours(-2 /*your Timezone*/)
Browser showed everything right.
Change your code to
HttpContext.Response.Cookies.Append("MyCookie",
"test cookie value",
new Microsoft.AspNetCore.Http.CookieOptions
{
Expires = DateTimeOffset.Now.AddDays(1).AddMinutes(-5)
});
//if you want to have the same expiration date as your server's
or use UtcNow + client's timezone
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.
I use a javascript function to store the cookie:
createCookie("teaser", "teaser", 7);
function createCookie(name, value, days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
document.cookie = name + "=" + value + expires + "; path=/";
return value;
}
And when I check the cookies in my browser the cookie expiration of teaser is correctly set:
25. oktober 2011 16:12:17
But when in C# i go to get the value, the expiration date is set to 01.01.0001.
var cookie = Request.Cookies["teaser"];
if (cookie != null && teaserList.Count() > 0)
{
cookie.Expires is 01.01.0001
Any clue?
Egghead says that:
The browser is responsible for managing cookies, and the cookie's
expiration time and date help the browser manage its store of cookies.
Therefore, although you can read the name and value of a cookie, you
cannot read the cookie's expiration date and time. When the browser
sends cookie information to the server, the browser does not include
the expiration information. (The cookie's Expires property always
returns a date-time value of zero.) If you are concerned about the
expiration date of a cookie, you must reset it.
The browser does not transmit the expiration date of a cookie to servers, this is as per HTTP specification.
Browsers only send the cookie name and value only.
The Expires property on the cookie object is only used when setting expiration date on a cookie that is going to be written in a response, not read in a request.
When reading the cookie is Expires property value is always going to be null. You can see this for yourself by examining the HTTP headers. If you really want to read the expire value of a cookie, try writing it in the value itself, or using another cookie with the value of the expiration date (you will need to keep them in sync yourself).