Message: ID4243: Could not create a SecurityToken. A token was not found in the token cache and no cookie was found in the context - wif

We're getting the exact same error as in this thread ... in our production environment.
[WIF Security Token Caching
Does anybody have a fix to this error ?
Message: ID4243: Could not create a SecurityToken. A token was not found in the token cache and no cookie was found in the context.
Here is some info about our setup:
• We‘re using built-in Windows Identity Framework with .NET Framework 4.5.1
• The problem is almost always associated with changing from RelyingParty#X over to RelyingParty#Y ( e.g. the moment user clicks the RP#Y he‘s SIGNED OUT without asking for it ) – when he logs in again after this event, he‘s taken right to the page he was asking for, inside RP#Y
• We‘re using e.SessionToken.IsReferenceMode = true; // Cache on server, to get a smaller cookie
• By using IsReferenceMode = true, our FedAuth cookie stores a „pointer“ to the actual Token which is stored inside our database
• We‘re using our own DatabaseSecurityTokenCache which is overriding the functions in SessionSecurityTokenCache. By using the the DatabaseSecurityTokenCache alongside the IsSessionMode = true, we‘re server-farm-friendly ( but we‘re also guaranteed to be within the same server through all our login-session ) so if the application pool for some reason dies, we‘re able to get the token from database through the DatabaseSecurityTokenCache. I‘ve verified this by completely killing IIS in the middle of a session ( with „net stop WAS“ and the restart it again with „net start W3SVC“ and we‘re still able to get the Token from the DatabaseSecurityTokenCache ). I‘ve also tried doing the same by simply using the out-of-the-box SessionSecurityTokenCache and that will fail respectivly ( as expected )
• Default token lifetime is 20 minutes ( but the user can change it to 40 or 60 minutes if he wants to ) – that will only be effective the next time the user logs in ( and 90% of our user are just using the default 20 minutes lifetime )
• We‘re using a certificate (same on all servers) to encrypt the FedAuth cookie, NOT a machine-key ( which would be catastrophic if using server-farm, with different machine-keys )
• so all the servers can decrypt cookies, which were encrypted from another server.
• We have a javascript with a countdown in our RelyingParty4 and RelyingParty5 ( two different relying parties ) which is used as a „timeout script“ in case the user leaves his computer unattended ... he will be signed out when the token is about to expire – (minus) 30 seconds ( e.g. 20 minutes – 30 sec = 19,5 minutes ) with idle time. This is protect our very sensitive banking information, so when the user comes back to his machine he will need to login again. e.g. We‘re also using sliding sessions ([http://www.cloudidentity.com/blog/2013/05/08/sliding-sessions-for-wif-4-5/]) and when we slide, the timing in the javascript of the client is also updated as well, to match the length of the token minus 30 seconds. These 30 seconds are used to make sure that the session is still alive when signing out, so it‘s a little bit shorter than the lifetime of the token/session. We currently sliding if this condition is met: total lifetime / 2 .... e.g. 20 / 2
• We‘re only sliding if there‘s any activity going on with the user ( i.e. he‘s moving around, doing some work ). We're sliding in minute10+ (if token lifetime is 20minuts) as the example above shows
• We‘ve debugged the problem multiple times and this is the WIF error we‘re getting: Exception: System.IdentityModel.Tokens.SecurityTokenException Message: ID4243: Could not create a SecurityToken. A token was not found in the token cache and no cookie was found in the context. Source: Microsoft.IdentityModel at Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver) at Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(Byte[] token, SecurityTokenResolver tokenResolver) at Microsoft.IdentityModel.Web.SessionAuthenticationModule.ReadSessionTokenFromCookie(Byte[] sessionCookie) at Microsoft.IdentityModel.Web.SessionAuthenticationModule.TryReadSessionTokenFromCookie(SessionSecurityToken& sessionToken) at Microsoft.IdentityModel.Web.SessionAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs eventArgs) at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
• We‘ve been able to re-produce the bug by using an old FedAuth cookie and this plugin: ( attention! We‘re not sure if this is the same thing that‘s happening on PROD, but at least it gives the same error in our Logging system ) This is good, but I think you should add the steps on how we‘re able to modify the content of the FedAuth cookie, to bring this problem to life, locally.- You can use this:
It‘s simple by taking the Value of the FedAuth cookie from some previous sessions ( on the same machine! not from another machine, that won‘t work ) And pasting it into the Value of the FedAuth cookie and refreshing the page.-
Plugin used to modify the cookie, in Chrome is called „Edit This Cookie“:
- If we change the content of this cookie to a value from a previous session, and hit the refresh ( CTRL + R in Chrome ) we get the infamous TokenSecurityException ID4243 and the RP calls for a immidiate FederatedSignout because we're unable to recover from this situation.
Signing out....
I should also probably mention that we took's Microsoft MSDN's article marked "Important" on IsReferenceMode seriously and added it also to our
SessionAuthenticationModule_SessionSecurityTokenCreated event:
e.SessionToken.IsReferenceMode = true;
taken from MSDN:
Important!
To operate in reference mode, Microsoft recommends providing a handler for the WSFederationAuthenticationModule.SessionSecurityTokenCreated event in the global.asax.cs file and setting the SessionSecurityToken.IsReferenceMode property on the token passed in the SessionSecurityTokenCreatedEventArgs.SessionToken property. This will ensure that the session token operates in reference mode for every request and is favored over merely setting the SessionAuthenticationModule.IsReferenceMode property on the Session Authentication Module.
Below is our whole SessionAuthenticationModule_SessionSecurityTokenReceived,
please examine the comments I put into it ... it explains what everything does:
void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender, SessionSecurityTokenReceivedEventArgs e)
{
if (e.SessionToken.ClaimsPrincipal != null)
{
DateTime now = DateTime.UtcNow;
DateTime validTo = e.SessionToken.ValidTo;
DateTime validFrom = e.SessionToken.ValidFrom;
TimeSpan lifespan = new TimeSpan(validTo.Ticks - validFrom.Ticks);
double keyEffectiveLifespan = new TimeSpan(e.SessionToken.KeyExpirationTime.Ticks - e.SessionToken.KeyEffectiveTime.Ticks).TotalMinutes;
double halfSpan = lifespan.TotalMinutes / 2;
if (validFrom.AddMinutes(halfSpan) < now && now < validTo)
{
SessionAuthenticationModule sam = sender as SessionAuthenticationModule;
// This will ensure a re-issue of the token, with an extended lifetime, ie "slide". Id deletes the current token from our databasetoken cache (with overriden Remove of the SessionSecurityTokenCache ) and writes a new one into the cache with the overriden AddOrUpdate of the SessionSecurityTokenCache.
// it will also write the token back into the cookie ( just the pointer to the cookie, because it's stored in database-cache ) because the IsReferenceMode = True is set
e.ReissueCookie = true; // Will force the DatabaseSecurityTokenCache'ið to clean up the cache with this, handler.Configuration.Caches.SessionSecurityTokenCache.Remove(key); internally in WIF's SessioAuthenticationModule
e.SessionToken = sam.CreateSessionSecurityToken(
e.SessionToken.ClaimsPrincipal,
e.SessionToken.Context,
now,
now.AddMinutes(lifespan.TotalMinutes),
false); // Make persistent, þannig að kakan lifir EKKI af browser-close / tab-lokun:
{
e.SessionToken.IsReferenceMode = true; // Cache on server
}
// Not needed, because if ReissueCookie = true; is set, it WILL to a WriteSessionTokenToCookie internally in WIF
//FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(e.SessionToken); // <---- er þetta e.t.v. bara það sem við þurfum ? Nei, á ekki að þurfa, er gert þegar tóki er búinn til með CreateSessionSecurityToken
}
else if (validTo < now)
{
// Fix
// http://blogs.planbsoftware.co.nz/?p=521
var sessionAuthenticationModule = (SessionAuthenticationModule)sender;
sessionAuthenticationModule.DeleteSessionTokenCookie(); // <--- is this really needed like the article says ? http://blogs.planbsoftware.co.nz/?p=521
e.Cancel = true; // This will allow a silent-login if the STS cookie is still valid, e.g. switching between RP's where we're switching from an active RP to a RP which has it's cookie outdated, but the STS's session is still alive. We don't want to prompt the user for a new login, beucase the STS session is still OK!
}
}

this post helped me, so it can help you and others those have this kind of error.
void Application_OnError()
{
var ex = Context.Error;
if (ex is SecurityTokenException){
Context.ClearError();
if (FederatedAuthentication.SessionAuthenticationModule != null){
FederatedAuthentication.SessionAuthenticationModule.SignOut();
}
Response.Redirect("~/");
}
}
From this link.
Hope it was useful!

---------- UPDATE, This is how Lord02 fixed the proplem -----------
The problem was that when users are coming in with stale cookies ( from a previous session, i.e. if they did NOT sign out from our system ... but instead just closed the tab ) and then logged in again,
our cookie which was in SessionMode = true ... tried to go to the DatabaseTokenCache to GET the whole token from database, but as I said our SSIS process deletes all Tokens which are OLDER than 12 hours old (outdated tokens!) so we don't have loads of orphan tokens, which are outdated in our database and are unusuable ... just taking up space in our database.
So after this deletion is done, each night, the DatabaseTokenCache GET‘s function would not return a valid Token ... and the user was signed out because of : ID4243: Could not create a SecurityToken. A token was not found in the token cache and no cookie was found in the context.
So instead of NOT deleting the Tokens inside our database I created a special handler, which intercepts this error on the RP‘s site ... and redirects the user back to the STS – which will then Create a brand new token and Write that down to the DatabaseTokenCacheStore, like this below
The exception with ID4243 is thrown when the cookie is set as “reference mode” AND the token is not present in the cache –
I can confirm that is by-design and also by-design WIF does not redirect the call to the STS (to start over the authentication process)
To overcome this problem I intercept this exception and react properly.
I redirect to the issuer if this error comes up inside a customSessionAuthModule I created for this:
public class CustomSessionAuthenticationModule : SessionAuthenticationModule
{
protected override void OnAuthenticateRequest(object sender, EventArgs eventArgs)
{
try
{
base.OnAuthenticateRequest(sender, eventArgs);
}
catch (SecurityTokenException exc)
{
// ID4243: Could not create a SecurityToken. A token was not found in the token cache and no cookie was found in the context.
if (exc.Message.IndexOf("ID4243", StringComparison.OrdinalIgnoreCase) >= 0)
{
// Returning directly without setting any token will cause the FederationAuthenticationModule
// to redirect back to the token issuer.
return;
}
else
{
throw;
}
}
}
}

Related

How to not make the user re-login every time browser closes?

This might be a basic/dumb question, but I don't know the right keyword to Google. What I want is that when I authenticate the user to my web app, they can close the browser, and when they open it back, they can still use my website - they are not logged out (yet).
I have been following the tutorials in IdentityServer docs (https://identityserver4.readthedocs.io/en/latest/quickstarts/2_interactive_aspnetcore.html), and so far I have managed to get the whole IDP-API-Client working. I have inspect the token that I get from IDP, it's valid for 2 weeks, so what am I missing here, why do I get logged out when I close the browser?
My guess is that I need to store the token to the cookie, but how do I save it, and how do I force the web application to always check for the cookie?
The IS4 tutorial has this:
services.AddAuthentication(o =>
{
o.DefaultScheme = "Cookies";
o.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", o =>
{
o.Authority = "https://localhost:5001";
o.ClientId = "mymvcclient";
o.ClientSecret = "mymvcclientsecret";
o.ResponseType = "code";
o.SaveTokens = true;
o.Scope.Add("myapi");
o.Scope.Add("offline_access");
});
I assume that's just creating a cookie, but how do I specify for it to save my token, and read the token from the cookie when user opens my web application?
Yea there is an option called ExpireTimeSpan in your cookie handler, which defaults to 14 days. You can change it to anytime longer than that by just setting a value to it:
...
.AddCookie("Cookies", options => {
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = false;
})
.AddOpenIdConnect("oidc", options => {
...
options.UseTokenLifetime = false;
...
})...
The above sets the cookie expiration to 30 days, instead of the default 2 weeks.
You also want to make sure the UseTokenLifetime option from the OIDC is set to false, which is the default for now I think. Tokens coming back from the Identity Server, for example, tend to have short lives. If you set it to true, which was default before, then it would override whatever expiration you set earlier.
The best term to look for on this is probably "persistent sessions", or just session management in general. This is something handled by ASP.NET Core, not really IdentityServer. There are several mechanisms to maintain session state on the server, which you'd need for example not to lose all sessions when a server restart happens.
I've had the best luck using the ITicketStore interface, which allows persisting the sessions to a database. The cookie ends up with a session ID which is validated on each request for expiration.

Correlation failed when logging in for the second time after hitting the back button on the browser

Ok, first question ever so be gentle. I've been struggling with this for about a week now and i am finally ready to accept defeat and ask for help.
Here's what is happening. I have an IdentityServer4 IDP, an API and an MVC Core Client. I am also interacting with 2 external OAuth2 IDPs provided by the business client.
My problem scenario is this:
The user logs in through my IDP(or potentially one of the external ones)
Once the user is in the Mvc Client they hit the back button on their browser
Which takes them back to the login page(whichever one they used)
They reenter the credentials(login again)
When redirected back(either to the MVC in the case of my IDP, or my IDP in the case of one of the external IDPs) i get RemoteFailure event with the message:correlation failed error
The problem seems, to me, to be the fact that you are trying to login when you are already logged in(or something). I've managed to deal with the case of logging in at my IDP, since the back button step takes the user to my Login action on the Controller(I then check if a user is authenticated and send them back to the MVC without showing them any page) but with the other two IDPs the back button does not hit any code in my IDP. Here are the config options for one of the OAuth2 external IDPs:
.AddOAuth(AppSettings.ExternalProvidersSettings.LoginProviderName, ExternalProviders.LoginLabel, o =>
{
o.ClientId = "clientId";
o.ClientSecret = "clientSecret";
o.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
o.CallbackPath = PathString.FromUriComponent(AppSettings.ExternalProvidersSettings.LoginCallbackPath);
o.AuthorizationEndpoint = AppSettings.ExternalProvidersSettings.LoginAuthorizationEndpoint;
o.TokenEndpoint = AppSettings.ExternalProvidersSettings.LoginTokenEndpoint;
o.Scope.Add("openid");
o.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
//stuff
},
OnRemoteFailure = async context =>
{
if (!HostingEnvironment.IsDevelopment())
{
context.Response.Redirect($"/home/error");
context.HandleResponse();
}
}
};
}
The other one is the same. Since the error is exactly the same regardless of the IDP used, i am guessing it is not something native to OIDC but to OAuth middleware and the code(config options) they share, so i am not going to show the OIDC config on the MVC client(unless you insist). Given how simple the repro steps are i thought i would find an answer and explanation to my problem pretty fast, but i was not able to. Maybe the fix is trivial and i am just blind. Regardless of the situation, i would apreciate help.
I could reproduce your issue.
When the user goes back to the login screen after successfully logging in,
it might well be that the query parameters in the URL of that page are no longer valid.
Don't think this is an issue specific to Identity Server.
You may read
https://github.com/IdentityServer/IdentityServer4/issues/1251
https://github.com/IdentityServer/IdentityServer4/issues/720
Not sure how to prevent this from happening though.

Session Authentication not working in Play when using Silhouette

I am using Silhouette security library. My Play server seem to send empty Session information in response. What am I doing wrong?
Following is the print on Play's console just before sending response.
Session(Map(authenticator -> 1-jtwBvA+LsLKE2rnkT/nMH1aQF9xc1twhECrma9mj3NUhUdVDmh/4wxQ2MxDOjcxkvEMTi1k63Dg5ezl+9FzDE3miaM5DbOrhyqAyGu4+30mHHV3QdPKA3IQQx5UdL1Hu85fZRI4f3Ef+q6xAgboDps0uBob5ojzo5Oqy8FNsoexn7Wr9iRyTr5xrMrLvl9GNQa+rA3q8qvW84sJaSei2iydrP2OjUbnnzo+zgrHLB3Bn7KJxOcFH4h9CikZNk/FHbtDm4uxzcK3paK1CuuIWLE8yvcYdavJ+4ejV5IaJ8QesJQRFgBktD9L/A2bc03eaA8wm)))
But in the the browser window, I notice that the value is empty.
Set-Cookie: PLAY_SESSION=; Max-Age=-86400;
Note that my browser earlier already had a PLAY_SESSION cookie from previous test runs. However, I would expect that the client application (Angular) would override old cookies with new cookies. Am I correct?
Following is the code snippet which creates, initialised and embed session information
val AuthenticatorFuture: Future[SessionAuthenticator] = silhouette.env.authenticatorService.create(loginInfo) //create authenticator
AuthenticatorFuture.flatMap(authenticator => { //got the authenticator
val securityTokenFuture: Future[Session] = silhouette.env.authenticatorService.init(authenticator) //init authenticator
securityTokenFuture.flatMap(securityToken=> {
println("adding security token: ",securityToken)
val result:Future[AuthenticatorResult] = silhouette.env.authenticatorService.embed(securityToken, Ok(Json.toJson(JsonResultSuccess("found user"))))
result
The Environment is defined as
trait SessionEnv extends Env {
type I = User
type A = SessionAuthenticator
}
As is passed to my controller as
silhouette: Silhouette[SessionEnv]
I created is at compile time as follows
val configSession = SessionAuthenticatorSettings()
val sessionAuthenticatorService = new SessionAuthenticatorService(configSession,fingerprintGenerator,authenticatorEncoder,new DefaultSessionCookieBaker(),clock)
val sessionEnv = com.mohiva.play.silhouette.api.Environment[SessionEnv](userIdentityService,sessionAuthenticatorService,Seq(),EventBus())
The issue is probably expected behavior of Play Framework as Silhouette doesn't modify the session cookie. I noticed that the browser already had a previous expired cookie and it sends it in the signin request. When Silhouette authenticator sees the expired cookie, it sends an empty value back. I think this is to make the browser discard the previous cookie.

Google realtime Model update onFileLoad

I see that the workflow is to start authrorizer, giving it file loader. So, we have a sequence of callbacks, onAuthrorized => start loading file => doc.getModel() on file load. Here they say how you get the model. But, I also see that gapi.drive.realtime.load(fileId, onFileLoaded, initializeModel, handleErrors) can elso end up with TOKEN_REFRESH_REQUIRED and it seems that TOKEN_REFRESH_REQUIRED can fire after the document is loaded, after some time of user inactivity, which seems to be related with token expiration. How should re-authorization to go? Should I tell the client that the current model that he is connected to is invalid? Please note that my app starts on file load. So, if I go the whole stack re-authorization, which calls another file load, which calls another document loaded will re-start my application. Is it supposed way to go? To put in other words, is there a way to refresh the token without loosing existing connection?
Where is the token stored actually? I do not see that I receive it on authorized. It is not passed to the realtime.load. How does realtime.load knows about the token? How can I speed up the token expiration for debug?
I am still not sure that this is a right answer but this is what I have got looking at code here, which says that we should provide empty callback to re-authorize
/**
* Reauthorize the client with no callback (used for authorization failure).
* #param onAuthComplete {Function} to call once authorization has completed.
*/
rtclient.Authorizer.prototype.authorize = function(onAuthComplete) {
function authorize() {
gapi.auth.authorize({client_id: rtclient.id, scope: ['install', 'file'],}, handleAuthResult)
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
hideAuthorizationButton() && onAuthComplete()
} else with (authorizationButton) {
display = 'block' ;
onclick = authorize;
}
}
You call it first use it in a function to load your document
(rtclient.authorizer ? rtclient.authorizer = identity : rtclient.authorize) (proceedToLoadingTheFile)
But later, on timeout we have code
function handleErrors(e) { with(gapi.drive.realtime.ErrorType) {switch(e.type) {
case TOKEN_REFRESH_REQUIRED: rtclient.authorizer.authorize() ; break
case CLIENT_ERROR: ...
Note no arguemnts in the latter. Authorizer won't reload the document. I think that this explains the logic asked. However, it does not answer about the internals, how is it possible that loader takes on existing authorizer or switches to a new one.

CI 2.0.3 session heisenbug: session is lost after some time 20 minutes, only on server redirect, nothing suspicious in the logs

I can't seem to make any progress with this one. My CI session settings are these:
$config['sess_cookie_name'] = 'ci_session';
$config['sess_expiration'] = 0;
$config['sess_expire_on_close'] = FALSE;
$config['sess_encrypt_cookie'] = FALSE;
$config['sess_use_database'] = TRUE;
$config['sess_table_name'] = 'ci_sessions';
$config['sess_match_ip'] = FALSE;
$config['sess_match_useragent'] = FALSE;
$config['sess_time_to_update'] = 7200;
$config['cookie_prefix'] = "";
$config['cookie_domain'] = "";
$config['cookie_path'] = "/";
$config['cookie_secure'] = FALSE;
The session library is loaded on autoload. I've commented the sess_update function to prevent an AJAX bug that I've found about reading the CI forum.
The ci_sessions table in the database has collation utf8_general_ci (there was a bug that lost the session after every redirect() call and it was linked to the fact that the collation was latin1_swedish_ci by default).
It always breaks after a user of my admin section tries to add a long article and clicks the save button. The save action looks like this:
function save($id = 0){
if($this->my_model->save_article($id)){
$this->session->set_flashdata('message', 'success!');
redirect('admin/article_listing');
}else{
$this->session->set_flashdata('message', 'errors encountered');
redirect('admin/article_add');
}
}
If you spend more than 20minutes and click save, the article will be added but on redirect the user will be logged out.
I've also enabled logging and sometimes when the error occurs i get the message The session cookie data did not match what was expected. This could be a possible hacking attempt. but only half of the time. The other half I get nothing: a message that I've placed at the end of the Session constructor is displayed and nothing else. In all the cases if I look at the cookie stored in my browser, after the error the cookie's first part doesn't match the hash.
Also, although I know Codeigniter doesn't use native sessions, I've set session.gc_maxlifetime to 86400.
Another thing to mention is that I'm unable to reproduce the error on my computer but on all the other computers I've tested this bug appears by the same pattern as mentioned above.
If you have any ideas on what to do next, I'd greatly appreciate them. Changing to a new version or using a native session class (the old one was for CI 1.7, will it still work?) are also options I'm willing to consider.
Edit : I've run a diff between the Session class in CI 2.0.3 and the latest CI Session class and they're the same.
Here's how I solved it: the standards say that a browser shouldn't allow redirects after a POST request. CI's redirect() method is sending a 302 redirect by default. The logical way would be to send a 307 redirect, which solved my problem but has the caveat of showing a confirm dialog about the redirect. Other options are a 301 (meaning moved permanently) redirect or, the solution I've chosen, a javascript redirect.

Resources