Spring RememberMe processAutoLoginCookie - spring-security

I'm using Spring Security 3.0.0 and persistent RememberMe. When the server restarts and a browser window is still open, we need to be able to continue using the application without having to login - if remember me is selected.
I'm getting a org.springframework.security.web.authentication.rememberme.CookieTheftException: Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack, when I try to continue to use the application after a server restart.
What I notice is that the processAutoLoginCookie method gets called twice. I'm not sure why. The behavior of the method itself seems to be correct, ie , update the token in the database and update the cookie in the client.
Any help on this would be appreciated.
Thank you.

I was getting the exact same issue! The processAutoLoginCookie was getting called twice in succession so that the first call was successful, but the second call fails because the cookie is updated by the first call.
My only solution was to subclass PersistentTokenBasedRememberMeServices and override the processAutoLoginCookie method. I had to copy the existing code for processAutoLoginCookie and comment out the throwing of the CookieTheftException.
Note: My solution will open up a security hole!
If you are happy to allow for Cookie Thefts to occur (my system is used internally and does not contain sensitive data) then this solution will work.
Alternatively, you could also subclass PersistentTokenBasedRememberMeServices and add a more robust solution that still checks for Cookie Theft Exceptions but allows the two successive calls to processAutoLoginCookie to be made.

Related

Missing ASP.Net cookies from iOS

I'm experiencing some trouble with an ASP.NET MVC 5 application that has been buggering me for a few days.
The application is using Forms Authentication to control whether a user is Authenticated, and Session to keep track of the user's data.
A few of our users are unable to log in, and I have been able to recreate the problem on a collegue's device. We don't know much about the devices with the problems, except that they all seem to be running iOS. I can't seem to nail down the pattern, though, as one user who reported the problem was running 11.2.5, which I am also running myself without trouble. The collegue's device is running 11.3 beta, but I haven't yet heard of any other users with this version, so I'm unable to pin point that as the problem - which also seems unlikely when one of the users with the problem is running 11.2.5.
The issue happens after the users try logging in. After a successful login, the user is redirected to a specific section of the site, where Authentication is required. Requesting any page in this section redirects you to the login page if you are not Authenticated.
Some of the resources that are requested on this page expect to retrieve some data from Session. For some reason, the Session cookie may sometimes be missing, and other times the Authorization cookie is missing.
For the past few days while I have been hunting this, my collegue's device happened to leave out the Session cookie when requesting our Cachemanifest, in which we expect to be able to generate a specific part based on some data from the Session. This caused the generation to fail, which in turn caused the request to fail.
After changing the generation logic to work around this, with a different solution if the Session cookie is missing, my collegue's device has changed its approach; Now, it includes the Session cookie when requesting this resource, but instead leaves out the Authorization cookie in a subsequent request to a different resource, which is still in the section that requires Authorization. This resource happens to be mentioned in the Cachemanifest, and because this next request does not include the Authorizaiton cookie, that request is redirected to the login page, which is interpreted by the Cachemanifest as an error.
As far as I recall, I haven't changed anything else, so I'm really at a loss as to why this devices' behaviour has changed.
Both of these cookies are included in other requests before and after the failing request, and there doesn't seem to be a pattern between what requests is chooses to leave these two cookies out of, or which cookies to leave out. The only thing that is consistent is that the behaviour doesn't change when retrying after clearing the browser data (only after I made my change, which I still don't see how could result in this behaviour).
The site is forcing HTTPS, so I tried setting the Session cookie to Secure, but that didn't seem to make a difference.
I still need to try setting the Authorization cookie to Secure, but I'm not very optimistic there. I also still need to try reverting my changes to see if the old behaviour reappears.
Has anybody experienced similar problems before, or does anybody have any further suggestions? I know I might be a bit sparse on the details, I'd be happy to provide any further details if I'm able.
Thank you in advance :)

Rejecting an otherwise successful ws-federation login in a CookieAuthenticationProvider()

I am working with an MVC5 application which is secured by the WsFederationAuthentication and CookieAuthentication OWIN components. As part of the login process, I inject some additional claims into the ClaimsIdentity returned from the SAML provider (ADFS in this case) during the OnResponseSignIn delegate of a CookieAuthenticationProvider supplied when I set CookieAuthenticationOptions during Startup. That allows me to manipulate the ClaimsIdentity in the OwinContext before it's used to generate the ticket. In practice, this works great for what I've needed to do in the past (mostly add custom claims depending upon values found in a database matched with data supplied from the incoming claim).
However, dependent upon circumstances that arise when OnResponseSignIn is executed, I now need to be able to reject the login in such a way that I can display an error to the user indicating why it's not possible for them to log in. This is proving to be much trickier than I had imagined.
These are the two main approaches I've tried:
Throw an exception in CookieAuthenticationProvider.OnResponseSignIn:
This works in that the authentication cookie is never set and my global error handler catches the exception and displays it to the user. However, I can't determine a way to effectively reject the identity before throwing the exception. So, my global error handler believes that the HttpContext is authenticated and renders the error view with elements that should not be rendered if the user is not authenticated. Nulling the OwinContext.Identity inside OnResponseSignIn does not have an effect as all I'm doing is clearing a value inside the OnResponseSignInContext which has no effect on the overall pipeline. So far as I'm aware, I can't mark the actual ClaimsIdentity itself as not authenticated as IsAuthenticated is set to true when you set the authentication type in the ClaimsIdentity ctor and can't be modified after that.
Add an "error" claim in CookieAuthenticationProvider.OnResponseSignIn as a bread crumb and then look for it in CookieAuthenticationProvider.OnValidateIdentity and throw the exception there.
This approach has the benefit of being able to clear the login (a benefit afforded by OnValidateIdentityContext.RejectIdentity()). So, now my error view renders correctly for an unauthenticated user. However, the problem here is that I can't figure out a way to prevent the authentication cookie created after OnResponseSignIn from being emitted to the client and set in the browser. The tools I have available in either of these delegates allow me to append and delete cookies from the OwinContext, but somehow this cookie is not one that I can do that with. It appears to be actually added to the response stream later in the pipeline where I can't change it. So, if I let OnResponseSignIn complete and then look for the cookie during the execution of OnResponseSignedIn, I don't see it there. Really not sure why that is -- looking at the source of CookieAuthenticationHandler, it looks as thought I should see it as it's added in between the execution of those two CookieAuthenticationProvider delegates. I also can't prevent the cookie from being generated in the first place as the CookieAuthenticationHandler will throw an exception if the ticket can't be serialized.
So, I either can get no cookie set and an as-if-authenticated error view or get an unauthenticated error view, but have a cookie set that effectively prevents the user from trying to sign in again.
Ultimately, I'm rolling around in the mud of the framework enough at this point that I feel like I must be approaching the problem incorrectly. What I'm looking for is an answer to this question:
How can I reject an otherwise valid WsFederation authentication postback and display an error to a user that explains what happened without emitting an AuthenticationTicket to the client?
I'm guessing there must be another point in the process (perhaps not in CookieAuthenticationProvider) where this can be done before the pipeline decides that authentication has succeeded.
Probably you use something like this in your Startup code:
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions())
The class WsFederationAuthenticationOptions provides the possibility to hook into the wsfed authentication (like the CookieAuthenticationProvider for the cookie authentication). You can set the Notifications property to a new instance of WsFederationAuthenticationNotifications. I recommend to use the SecurityTokenValidated property and set it to your implementation i.e. a new method matching the required Func<> definition. This is the point after the token passed validation and a ClaimsIdentity has been generated. Then you can do your additional checks and set the AuthenticationTicket of the methods parameter SecurityTokenValidatedNotification to null and modify the HttpResponse. Than the CookieAuthenticationHandler should not be triggered.

User.IsInRole fails for one action but not for another

I've put a custom authorize attribute into my MVC application, which does
if (!this.Roles.Split(',').Any(filterContext.HttpContext.User.IsInRole))
and if true it will redirect you to unauthorised.
I put this attribute on my controller at controller level.
One action works fine, and in one action I get unauthorised.
Is there some bug or problem in the role system? I've read that logging out and in can force some cache to refresh or something, however the system I am using authenticates with your domain credentials so there is no way to log out.
I've tried restarting the app pool and deleting my session cookie but nothing has worked.
Has anyone experienced this specific issue before or has some guidance on perhaps flushing any caching related to it (assuming it's a caching issue)?
ETA: Another user on the system gave himself the role required for the controller and both actions work fine for him. So perhaps my user is somehow bugged. This is on UAT so slightly more difficult to debug than running on my local machine (which works fine).
ETA2: I'm pretty sure this is a caching issue, as going to the URL with ?1=1 in the query string, it works. I'm unable to invalidate the cache though. This may be a problem in the future when assigning roles to people.
First, we need more code before we could possibly give you any definitive answers. However:
Caching could be a problem. If you're using anything like OutputCache, you should ensure that you're using VaryByCustom and somehow including the user's id or other identifying token in the custom string returned.
If you're adding roles to users, you must either log the user out and sign them back in, or otherwise invalidate their authorization. In Identity, for example, you can simply invalidate the security stamp, which will cause the user to be reauthorized, updating things like claims or roles that have changed since they signed in.

Random "The anti-forgery cookie token and form field token do not match" in Azure

We are getting random "The anti-forgery cookie token and form field token do not match" errors showing up in the error log in with our site hosted in Azure. After realizing that we needed a static machine key, we added to that to the web.config with the validationKey and decryptionKey attributes, however, we still have the random error popping up.
Just to define my use of "random" here, out of every ~200-300 form submissions, this happens once or twice. It just feels like too much to have happen and it's a real interruption to our clients that trust our services.
One other thought that crossed my mind is whether this is happening on machines that don't have cookies enabled. I haven't been able to verify that one way or another, but I didn't know if cookies are a requirement for the ValidateAntiForgeryToken to work. If it does require cookies, then should we pop up a message to our users letting them know that cookies are required for proper use?
I could use help coming up with ways to diagnose this or other ideas of how to handle this.
Thank you in advance.
[UPDATE] I just heard from a user where I saw this error popup. It turns out that they loaded the page and walked away for a while causing the error. That's great news since that means that the validation is just doing its job and nothing crazy was happening...I just need to verify if that data point is indicative of the rest of the users. So given that, how do you all handle situations where the token expires? Do you notify the user in some clean way?
Do you have a server farm ? i.e. do you have an App Service with auto-scaling enabled ? or a cloud service with several machines ?
If yes, check that all your machinekeys are defined with the same value in all your web.config files. The machinekey is used to generate AntiForgery tokens.
Could you log the headers of the failed requests ? You can add some code in your global.asax on Application_Error like :
foreach (string header in request.Headers)
{
// header <== header name
// request.Headers[header]) <== header value
}

Why does HttpAntiForgeryException occur randomly even with a static Machine Key?

We have an ASP.NET MVC 2 (.NET 4) application running on Windows Azure (latest 2.x OS version) with two web role instances.
We use the anti-forgery token supplied by MVC for all POST requests, and we have set a static Machine Key in web.config, so everything works on multiple machines and across restarts. 99.9% of the cases it works perfectly.
Every now and then, however, we log a HttpAntiForgeryException, with message "A required anti-forgery token was not supplied or was invalid."
I know the problem might be cookies not being allowed in the browser, but we've verified that and cookies are enabled and being sent back and forth correctly.
The error occurs with a variety of browsers and obviously causes problems to the users because they have to repeat the operation or they can lose some data. Suffice it to say, we haven't been able to reproduce the problem locally, but it only happens on Windows Azure.
Why is that happening? How can we avoid it?
I ran into this recently as well and found two causes.
1. Browser restores last session on open for page that is cached
If you have a page that is cachable that performs a post to your server (i.e. antiforgery will be on) and the user has their browser set to restore last session on start up (this option exists in chrome) the page will be rendered from cache. However, the request verification cookie will not be there because it is a browser session cookie and is discarded when browser is closed. Since the cookie is gone you get the anti-forgery exception. Solution: Return response headers so that the page is not cached (i.e. Cache-Control:private, no-store).
2. Race condition if opening more than one tab on start up to your site
Browsers have the option to open a set of tabs at start up. If more than one of these hit your site that returns a request verification cookie you can hit a race condition where the request verification cookie is overwritten. This happens because more than one request hits your server from a user that does not have the request verification cookie set. The first request is handled and sets the request verification cookie. Next the second request is handled, but it did not send the cookie (had not been set yet at request time) so the server generates a new one. The new one overwrites the first one and now that page will get an antiforgery request exception when it next performs a post. The MVC framework does not handle this scenario. This bug has been reported to the MVC team at Microsoft.
The anti forgery token contains the username of the currently connected user when it is emitted. And when verifying its validity, the currently connected user is checked against the one used when the token was emitted. So for example if you have a form in which the user is not yet authenticated and you emit an anti forgery token, there won't be any username stored in it. If when you submit the form you authenticate the user, then the token will no longer be valid. Same applies for logging out.
Here's how the Validate method looks like:
public void Validate(HttpContextBase context, string salt)
{
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
string str2 = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);
HttpCookie cookie = context.Request.Cookies[str2];
if ((cookie == null) || string.IsNullOrEmpty(cookie.Value))
{
throw CreateValidationException();
}
AntiForgeryData data = this.Serializer.Deserialize(cookie.Value);
string str3 = context.Request.Form[antiForgeryTokenName];
if (string.IsNullOrEmpty(str3))
{
throw CreateValidationException();
}
AntiForgeryData data2 = this.Serializer.Deserialize(str3);
if (!string.Equals(data.Value, data2.Value, StringComparison.Ordinal))
{
throw CreateValidationException();
}
string username = AntiForgeryData.GetUsername(context.User);
if (!string.Equals(data2.Username, username, StringComparison.OrdinalIgnoreCase))
{
throw CreateValidationException();
}
if (!string.Equals(salt ?? string.Empty, data2.Salt, StringComparison.Ordinal))
{
throw CreateValidationException();
}
}
One possible way to debug this is to recompile ASP.NET MVC from its source code and log exactly in which of the if cases you enter when the exception is thrown.
I have a few MVC3 web apps that get this pretty regularly also. The majority of them are because the client doesn't send a POST body. And most of these are IE8 because of some bug with ajax requests preceding a regular form post. There's a hotfix for IE that seems to address the symptoms, which sort of proves that it is a client bug in these cases
http://support.microsoft.com/?kbid=831167
There are a few discussions about the issue around the web, nothing too useful though, I definitely am not about to mess with keep-alive timeouts which is a suggested "solution" in some places...
https://www.google.com/search?q=ie8+empty+post+body
I've never been able to reproduce it with a variety of attempts to reset connections between POSTS so I'm afraid I don't have a real solution for the case of the IE empty POST bodies. The way we've mitigated it a little bit is to make sure that we never use the POST method when just retrieving data via ajax.
If you log the full request, check to see if the POST body is empty, and if it is, it'll probably be an older IE. And I don't mean Content-Length: 0, it will usually have a Content-Length that seems correct in the headers but there will literally be nothing after the headers in the request.
The issue as a whole is still a mystery to me though because we still get the occasional exception where there is a complete POST body. Our usernames never change and our keys are static as well, I haven't tried adding debugging to the source, if I ever get around to that I will report my findings.
There are a couple of options for what you could try. You could try remoting into the machine and looking at the event log to see if you can get more information from that in regards to where this is happening. If that doesn't help, you can use DebugDiag or some other tool to capture a dump of the process (DebugDiag will let you capture one at the time of this specific exception). And then look at that to see what is going on.
If you can't seem to figure it out from there, you can always create a support case with Microsoft to help you investigate it.
I have encountered similar problems with my home-brewed anti-forgery code, which is conceptually very similar to the MVC mechanism. Mostly the problem seems to occur because modern browsers appear willing to display cached copies of pages specified as non-cached.
I have tried all combinations of page no-cache directives, but sometimes I still get cached pages displayed.
I have found that a better solution is to hook the onbeforeunload event for the page and explicitly clear the value of the hidden input field holding the token value in the DOM.
If a cached copy of a page is loaded, it seems to contain the cleared input field value. I then test for this in the document ready function and reload the page if necessary:
window.location.reload(true);
Seems to work quite effectively, and I suspect it might for the MVC anti-forgery code too.

Resources