Umbraco 7 custom cookies - asp.net-mvc

I am running an MVC site along side Umbraco. The MVC site handles its own authentication completely separate to Umbraco, and ASP.NET Forms authentication for that matter. It sets a cookie and uses that internally to keep track of things.
Everything works fine for the most part, but if I am logged into my MVC site with the aforementioned cookie set, I try to login to the Umbraco admin section using the correct Umbraco credentials, it authenticates me and redirects me to the admin section but the WebAPI calls start to fail. The first is a call to: /umbraco/backoffice/UmbracoApi/UpdateCheck/GetCheck which returns a 417 Missing token null HTTP error response.
If I delete my custom cookie and refresh the page everything works fine.
I don't understand how my cookie can interfere with Umbraco's. It's not using ASP.NET Forms authentication or anything.

This error occurs because your request is not sending up the required angular CSRF headers + cookie. I'm not sure why this would be the case but it does seems strange if it is a fault of your custom cookie. Perhaps you can tell us some more information about your issue: Cookie name/value, steps to reproduce, specific version of Umbraco, hosting environment, etc....
Some info as to what is going on, the code that returns this error is here:
https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs#L94
This is where the CSRF cookies are set:
https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs
and this attribute is applied to two actions, one for login and one when we retrieve the current user data:
https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/Editors/AuthenticationController.cs#L103
https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/Editors/AuthenticationController.cs#L84
This is where the header is set in the JS:
https://github.com/umbraco/Umbraco-CMS/blob/5b9a98ad6ae9e63322c26f7b162204e34f7fcb54/src/Umbraco.Web.UI.Client/src/init.js#L11
Depending on your hosting environment/setup there has been strange reports of some firewalls stripping/changing data, for example:
http://our.umbraco.org/forum/umbraco-7/using-umbraco-7/47340-Umbraco-7-plus-ISA-Server-2006
Hopefully given the info above you might be able to pinpoint where the problem starts.

My initial thought is that you by accident used a key value for your cookie that is reserved by Umbraco, which could result in the wrong cookie being read, causing issues. The solution to this would be to simply rename your cookie.
If this is not the case I have another theory:
HTTP requests will always include all cookies which path/domain matches the domain of the resource you are requesting. They are sorted by path length primarily, and secondarily by creation time. If Umbraco backend for some reason finds the cookie used for authentication by its index number (wouldn't even be surprised) in the list, rather than key value, your custom cookie would cause the index to shift, thus making Umbraco look at the wrong cookie
So, if renaming the cookie didn't do anything, a fun thing to try could be to set path of the cookie to the shortest possible path, which would make your browser put the cookie further down the list, so the index won't shift.
It's just a theory though, so I'm interested in hearing how it goes :)

Related

MVC anti-forgery token

I've got a public MVC 5 web-site, using the anti-forgery token. Every day a large number of errors are logged in the form of "The anti-forgery cookie token and form field token do not match.", and a lesser number in the form of "The required anti-forgery cookie "__RequestVerificationToken" is not present.".
The problem is not reproducible, it occurs for different people on different pages at different times. Closing the browser resolves the problem - sometimes just using the Back button and re-trying resolves the problem.
As the website works for the vast majority of users, I can rule out missing ValidateAntiForgeryToken attributes in controllers, likewise, I can rule out missing or duplicate #Html.AntiForgeryToken() code in the views.
The website runs on a single server, so I can rule out different machinekeys in the web.config (I've tried running the website with and without this setting anyway).
The application pool is set to restart each night, and there's plenty of spare resource on the
server, so I can rule out the application pool restarting and invalidating sessions (especially as this isn't logged in the event log or anywhere else).
I've hit the problem very rarely - I definitely have cookies enabled, so I can rule out cookies being disabled. I can also rule out javascript being disabled, as user's can only progress so far into the site without JS - and errors occur on pages beyond this point.
I've disabled all caching, setting nocache, nostore etc. This seemed to reduce the occurrence of the issue, but it still persisted (I had to re-enable caching for a variety other reasons).
What other options are there to consider?
I am so frustrated by this I am considering turning off anti-forgery protection and contributing to the global weakening of security.
Make sure you have AntiForgery attributes both in controller and forms.
If you are doing ajax post maybe you can send RequestValidationToken as a parameter.
$('input[name=__RequestVerificationToken]').val()
Also maybe somebody attacking your site or using some bots to get content or post forms.

How does the MVC anti forgery token survive between web server restarts?

I've implemented anti-forgery protection using the ValidateAntiForgeryTokenAttribute in MVC 5. It is working fine, but in the future we may move to more of a "web farm" approach to hosting. If I run my application in development and go to a form, restart the web server (by restarting the app in Visual Studio) and then submit a form, it doesn't throw the System.Web.Mvc.HttpAntiForgeryException.
Our application doesn't use any other session state. Can someone help me understand how my server picks up where it left off? I'm not defining a machineKey in my web.config, or anywhere else that I can find. Does it have something to do with running in a development environment?
The only references I can find to this are for earlier versions of MVC, so I'm wondering if this is solved in a different way now.
I'm glad this functionality works, but I need to understand why.
The server itself isn't remembering anything; it doesn't have to.
The two things at work here are:
The form hidden input
A cookie
This means that if the user visits a page with an AntiForgeryToken on it, then the server restarts, it's no problem because the user's and the form's __RequestVerificationToken are still the same as they were.
The actual security token is a hashed key that is stored inside the AntiForgeryToken object. This object is serialised to Base 64 and that is what you see when you look at the values of the __RequestVerificationToken. Since the security keys are stored each time, even if the server resets the values are still inside those objects. The keys are then retrieved and compared in order to validate the tokens.
There is no decryption during this process. The tokens are deserialised, the security keys read and then compared. Since the security keys are not encrypted, but are rather hashed, then they cannot be decrypted; only compared.

MVC3 mixed forms and Windows authentication

I currently have an intranet site that is accessed by external customers. I therefore set this up using Forms Authentication. However the powers that be (my bosses) want all our domain users to not have to enter their username and password to access the site.
I've done a bit or reading and everything seems to point to setting up a WinLogin.aspx page that you alter to use WindowAuthenthication and then redirect from there.
I have a problem with this as I don't like the idea of putting an aspx form in my mvc application.
Can anyone tell me how to achieve mixed authentication using a strictly MVC Controller/Action setup without a second application?
NOTES: running MVC 3 on an IIS 7 box.
Forms Authentication is not related to the URL or physical structure of your files. What matters is that a URL should ultimately map to a physical (or virtual) resource on the server, and be processed, and be returned back to the user.
Thus, somewhere in between for each incoming call (each HTTP request, even those for CSS and JavaScript files), you have to see if the current user has enough permission to access it or not. If no, then you might redirect him to the login page.
If you want, you can have a URL like /user/windowslogin where user is the name of the controller, and windowslogin is the name of your action method. Then you can create a custom authentication attribute (something like [WindowsAuthentication]) on your windowslogin action, and in that attribute (which is an MVC filter in essence), you can see if the current request comes from within your domain, and if so, talk to Active Directory for authentication or stuff like that, and on case of successful authentication, create an authentication cookie using FormsAuthentication class, and the rest of the story.
However, I don't think this would be an easy task. Others might introduce better solutions.

HttpSession without cookie

I have an application a the moment which for a particular set of reasons will be interacting oddly with the hosting server.
The application is to be accessed through a larger portal and can be encapsulated within the portal display, however it makes extensive use of AJAX requests which are not intercepted by the portal. These requests are made directly to the hosting server, however I am seeing a problem.
When the first ajax request is made (a little way into the application flow) the Ajax request is not carrying with it the JSessionId cookie (obviously as it's sending this to a different server than it received it from)
Is there a good grails way to find the session the AJAX call should be interacting with. I have tried setting grails.views.enable.jsessionid to true, but this only works if the browser is not accepting cookies.
Create a hidden form input value that has the jsessionid in it on the page you send back to the portal on the first request. Then read that form variable, and set the cookie in your javascript code that makes the AJAX request.
I'm guessing seeing that this already works, cross-site scripting isn't an issue? AJAX requests to domains other than that which the main page originated from will be blocked by the browser.
The most reliable way will be for you to set up your own "cookie" and pass that along with the requests.
It sounds like you are running into issues due to the portal and it's cookies and then having to continue that "session" onto a different server. Your application needs to simply handle it's own sessions itself in order to prevent getting stomped on by the "normal" cookies.
The idea is essentially to create a session token when the portal makes a request from to your application, and then the subsequent AJAX calls your application makes back to it's own server should include that token. You can then easily associate that token with the session you need to be using.
If you are looking to make it a bit more robust and handle it above the level of your application, you can leverage the fact that Grails is built on Spring MVC deep down and override the default session handler to pick up on whatever mechanism you decide to go with. I'm not sure of exactly how to do this with Grails, but I've done similar things on Spring MVC projects and it isn't too tough once you get your head wrapped around the various injection points of the framework.
It isn't ideal, since there is now a fair bit more complexity, but in theory, the benefits of the portal are outweighing the added complexity required for traditionally "handled" things like sessions and expiring them, etc.

Copying cookies from main domain to subdomain

My application has a userspace which used to be accessed by a url like domain.com/~username, but I am in the process of converting that to using subdomains instead (username.domain.com). However, I am running into an issue that I'm hoping someone might have an idea of how to get around.
Currently, visitors to a user's site get a cookie of the form user<id>_authentication (where <id> is the user ID of the site they're visiting), which is set to have the domain www.domain.com. However, now that I'm switching to subdomains, I want to find those cookies and transfer them to a new cookie called authentication per subdomain, using the subdomain as the cookie domain. However, the rails cookies array does not find the main domain cookies.
I know that if the old cookies were using .domain.com as the domain instead, they'd apply to the subdomain and would be present in cookies, but these cookies are already existing, and I'm trying to make the change as seamless for a user as possible -- so if they had an authentication cookie already for a site, I want them to not have to reauthenticate if at all possible.
Is there any way I can get the cookies from the main domain or does anyone have another suggestion of how I can transfer the cookies?
Update: Sorry, I didn't make it clear before, the cookie is only set if the visitor actively authenticates themselves by submitting a form on the user's site.
If you change the cookie domain to be more permissive (applying to more sub domains) you have no way to read the old, more restricted cookies except from the top level domain that used to work.
You will have to read the cookie, authenticate, and then write a new more permissive cookie before the cookie can be read by the subdomain.
You can roll out your migration logic in advance of the feature and hope you get most people. The rest will have to re-authenticate manually.
Personally I think they should have to re-authenticate.. it will only happen once, then they'll have the new ".domain.com" cookie.
But... One way to achieve this would be to check for the new cookie and when failing to find it, redirect to a new page on the main domain, providing the return url.
In that new page, check for the old style cookie, set the new style cookie, and redirect to the original url. if they don't have the old style cookie, redirect to the login area.
hope this helps.

Resources