I'm having some really weird cookie issues on a website. It's an ASP.NET website, uses Umbraco 6.1.6 as its CMS but is mainly custom code and uses Forms Authentication for user login. It runs on IIS Server 2012 R2.
On successful registration, the user credentials are passed to the login method, which sets a cookie like this:
var authTicket = new FormsAuthenticationTicket(realUsername, false, 60);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
{
HttpOnly = true,
Secure = FormsAuthentication.RequireSSL,
Path = FormsAuthentication.FormsCookiePath,
Domain = FormsAuthentication.CookieDomain,
Expires = authTicket.Expiration
};
HttpContext.Current.Response.Cookies.Set(cookie);
It then does a redirect, trying to go to wherever the user was previously on the site before registering.
return Redirect(redirectUrl);
From the redirected page, it checks the request cookie to see whether the user was logged in:
HttpCookie authCookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
Sometimes the authCookie contains the session data, and works for long periods of time..... and other times it is null - for equally long periods of time - so it only intermittently works.
This led me to believe that perhaps IIS is having trouble checking the request cookie on redirect - because the cookie has not yet reached the user's browser. But various docs and other SO posts tell me this should work when I am using a ResponseRedirect type redirect.
The bit where it gets weird is that when I've just registered but the server thinks I'm not logged in, I can look at the cookies for this "not logged in" page in Chrome dev tools, and it's true - there's no cookie set - however, when you click on the site's "Login" button from the "not logged in" page... which forces it to check whether the user is logged in again, this time "authCookie" contains cookie data. Right after Chrome told me this data was not present when I submitted the page! The cookie then reappears in dev tools when the "logged in" page is returned.
At this point, I don't know whether it's that I can't trust Chrome dev tools, or it's that there's some odd browser caching / IIS caching going on, or what. I tried looking up cookie / redirection issues and they only seem to come up with newer versions of IE.
I've tried closing the browser, closing visual studio, and that doesn't seem to have a direct impact.
One last bit of information - I read in one post here that there are sometimes issues when the registration / login is performed on one thread but the code after registration is performed on another thread. I checked the thread debugger and found that indeed the registration / login were being performed on different threads... but this was observed when it was working.
The most frustrating part is when it suddenly starts working for a few hours - and there's seemingly nothing I can do to break it again to try out different things. (Which is where I am now! I wanted to at least try it in different browsers...)
Related
I have an authentication cookie that gets set after I hit a login screen in my local environment. That cookie has been set to be persistent and has been given a timeout period of 7 days in the future.
When I end my debug session and start debugging after another build the cookie is not present. This happens for every browser. Is there a way to get Visual Studio to remember the persistent cookie after a debug session completes?
The solution I found was to make it so that new instances of .NET Core MVC would not open up in a brand new window, but an existing one. I changed one setting
1)Tools menu
2)Options...
3)Debugging > General
4)Uncheck "Enable JavaScript debugging for ASP.NET"
And when I run the app with F5 an instance fires up in an existing instance of chrome and I can reuse the cookies that are already in existence. With that box checked it always opens into a new instance of chrome and cookies are not present.
Assuming you are using VS and ASPNet 4.5 or core 1.0/2.0 under IIS, check your debug output on start up and you might see :
“Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailable when application exits.”
This is caused by the DataProtection keys used by IIS. Follow this short blog post to resolve
Let’s have a quick look how to make cookies as persistent
//Creting a Cookie Object
HttpCookie _userInfoCookies = new HttpCookie("UserInfo");
//Setting values inside it
_userInfoCookies["UserName"] = "Abhijit";
_userInfoCookies["UserColor"] = "Red";
_userInfoCookies["Expire"] = "5 Days";
//Adding Expire Time of cookies
_userInfoCookies.Expires = DateTime.Now.AddDays(5);
//Adding cookies to current web response
Response.Cookies.Add(_userInfoCookies);
Now once you have set with the Cookies expires time , it will be stored in hard drive until expires or user manually delete or clear all the cookies. If you want your cookies need to be expires before the expiration time that you have mentioned earlier, you just need to override the cookies information.
HttpCookie _userInfoCookies = new HttpCookie("UserInfo");
//Adding Expire Time of cookies before existing cookies time
_userInfoCookies.Expires = DateTime.Now.AddDays(-1);
//Adding cookies to current web response
Response.Cookies.Add(_userInfoCookies);
So Just Work on Expiration.
and take a look at This
Consider the following scenario:
A user logs in to my webapplication, successfully. Browses around for 1 minute then closes the browser tab. (The cookie has been set, from loggin in).
Right after, I deploy a new update to the webapplication.
The user now visits the site again, just after deployment.
The site times out.
The timeout occurs until the browser releases the cookie.
If the user deletes the cookie, the user goes directly to the login page, without hassle.
I've tried finding out whats going on.
The problem seems to occur during decryption of the authentication ticket.
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt( authCookie.Value );
In global.asax Application_PostAuthenticateRequest
I cannot for the life of me figure out how to get past this.
Any help is greatly appreciated.
UPDATE:
I had a theory based on the MachineKey not being set explicitly, which means that when the AppPool recycles, the MachineKey changes.
Setting MachineKey fixed my issue.
I've noticed that there are some real inconsistencies between browsers in terms of cookies.
This is going to be rather long so bear with me.
Note: I've setup a domain in my host file called "testdomain.com", this bug WONT work when using "localhost".
Note2: I am curious to know how this works on Apache/PHP if when you retrieve a cookie by name if it gives a collection of cookies back.
Wikipedia
Wikipedia states that: http://en.wikipedia.org/wiki/HTTP_cookie#Domain_and_Path
Domain and Path
The cookie domain and path define the scope of the
cookie—they tell the browser that cookies should only be sent back to
the server for the given domain and path. If not specified, they
default to the domain and path of the object that was requested.
So if we push down:
Response.Cookies.Add(new HttpCookie("Banana", "2")
{
});
We should get a cookie with the domain used being the domain from the requested object, in this case it should be "testdomain.com".
W3
W3 states in the specification for cookies: http://www.w3.org/Protocols/rfc2109/rfc2109
Domain=domain
Optional. The Domain attribute specifies the domain for which the
cookie is valid. An explicitly specified domain must always start
with a dot.
So if we push down:
Response.Cookies.Add(new HttpCookie("Banana", "1")
{
Domain = Request.Url.Host
});
We pushed down the host-name explicitly, we should get a domain name set on the cookie which would be prefixed with the dot, in this case it should be ".testdomain.com".
It also states what's on Wikipedia:
Domain Defaults to the request-host. (Note that there is no dot at
the beginning of request-host.)
With me so far?
If I use the first method, defining a Domain:
Response.Cookies.Add(new HttpCookie("Banana", "1")
{
Domain = Request.Url.Host
});
This is the results:
IE9: 1 cookie
Opera: 1 cookie
Firefox: 1 cookie
Chrome: 1 cookie
As you can see, both Opera and IE both set an EXPLICIT domain without the dot prefix.
Both Firefox and Chrome DO set the EXPLICIT domain with a dot prefix.
If I use the following code:
Response.Cookies.Add(new HttpCookie("Banana", "2")
{
});
IE / Opera: Both have the exact same result, the domain WITHOUT the dot prefix.
Funnily enough, Firefox and Chrome both create cookies WITHOUT the dot prefix.
(I cleared all cookies and ran the code again)
Firefox:
Chrome:
INTERESTING BIT
This is where it gets interesting. If I write the cookies one after another like so:
Response.Cookies.Add(new HttpCookie("Banana", "1")
{
Domain = Request.Url.Host
});
Response.Cookies.Add(new HttpCookie("Banana", "2")
{
});
PERSONALLY I would expect one cookie to exist in the browser, because I assume it's based on the cookie name.
Here's what i've observed:
In IE / Opera, the LAST cookie set is the cookie that is used. This is because the Cookie name and Domain name are identical.
If you explicitly define a domain name with a dot, both browser will still see 1 cookie, the last cookie of the same name.
Chrome and Firefox on the other hand, see more than 1 cookie:
I wrote the following JavaScript to dump the values to the page:
<script type="text/javascript">
(function () {
var cookies = document.cookie.split(';');
var output = "";
for (var i = 0; i < cookies.length; i++) {
output += "<li>Name " + cookies[i].split('=')[0];
output += " - Value " + cookies[i].split('=')[1] + "</li>";
}
document.write("<ul>" + output + "</ul>");
})();
</script>
These are the results:
IE - 2 cookies set (browser sees 1):
Opera - 2 cookies set (browser sees 1):
Firefox - 2 cookies set and browser sees 2!:
Chrome - 2 cookies set and browser sees 2!:
Now you're probably wondering wtf all this is.
Well:
When you access the cookie by Name in C#, it gives you 1 cookie. (the first cookie that has that name)
The browser sends ALL cookies to the server
The browser doesn't send any information other than the key/value of the cookie. (this means the server doesn't care about the domain)
You can access both cookies of the same name, if you retrieve them by index
The problem...
We had to change our Authentication to specify the domain in the cookie when we pushed it down.
This broke Chrome and Firefox, users were no longer able to login, because the server would try authenticate the old auth cookie. This is because (from my understanding) it uses the Authentication Cookie Name to retrieve the cookie.
Even tho there are two cookies, the first one is retrieved which happens to be the old one, authentication fails, user isn't logged in. SOMETIMES the correct cookie is first in the list, and the authentication succeeds...
Initially we solved this by pushing a cookie with the old domain to expire it. This worked in Chrome and Firefox.
But it now broke IE/Opera since both browsers don't care about the domain and only compare the cookie based on the name.
My conclusion is that the domain on a cookie is a complete utter waste of time.
Assuming that we must specify the domain, and we can't rely on users to clear their browser cache. How can we resolve this problem?
Update:
Digging into how .NET signs a user out.
if (FormsAuthentication._CookieDomain != null)
{
httpCookie.Domain = FormsAuthentication._CookieDomain;
}
It looks like it's entirely possible for the Forms authentication to push an expired Auth cookie, that is entirely unrelated to the cookie the user is authenticated with. It doesn't use the current Auth Cookie's domain.
Which it can't use anyway, since the domain isn't pushed back to the server with the cookie.
Update 2
It seems FormsAuthentication is really broken. If you use an explicit domain name on a cookie when you authenticate the user, wait for the session to timeout, then refresh the page, the method of generating the cookie used by FormsAuthentication results in the domain being null which causes the browser to assign a dotless domain.
It requires that Forms be assigned a domain up front for it to be assigned to the cookie, this breaks a multi-tenant system...
#WilliamBZA's suggestion helped solve the initial problem, but then signout/session timeout bug that results in the cookie creating an implicit domain cookie has made me come to the conclusion that the solution is...
Don't use Explicit cookies in .NET... ever
There are far too many problems, sure they can be solved by being explicit on the Form/Domain, Cookie/Domain, etc. To ensure that the correct domain is used everywhere. But if your application hosts multiple domains or is multi tenant, then it just becomes too problematic.
Lesson is learnt. Don't use explicit cookies.
Can't help with why the cookies are treated differently, but a quick fix would be to use a different cookie name per sub-application rather than using the domain of the cookie.
In the case of Forms Authentication, change the name of the ASPXAUTH cookie.
This has been a nagging issue for some time, but very sporadic and difficult to isolate.
From time to time, browsers that have authenticated on a web application, have been open for a while, have logged in and out of the same web application multiple times, have multiple tabs, are pretty much any browser (Chrome, IE, Firefox, Safari), and seemingly at random, lose their ability to retain an AuthCookie after being set and followed by a redirect. Closing the browser and starting a new session resolves the issue, as does opening up a different browser and attempting to authenticate.
Our team uses forms authentication for all of our websites and web application. This is a pretty typical setup where a login form is displayed, the user enters credentials and a cookie is set on the click event of the postback, then a redirect occurs to the same page where the cookie is then referenced and used to complete authentication.
In this situation
FormsAuthentication.FormsCookieName = ".WebAuth"
Within Event:
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, Username, DateTime.Now, DateTime.Now.AddMinutes(SessionTimeout), false, Username);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
Response.Cookies.Add(faCookie);
Response.Redirect(Request.RawUrl, true);
After the redirect, on PreInit:
HttpCookie authCookie = Request.Cookies[cookieName];
At this point, the authCookie variable is typically not null, but in these isolated circumstances that I've outlined above, the cookie comes back null after the redirect.
This happens very randomly, sometimes weeks before affecting one of our developers. As I said, restarting the browser resolves the issue.
Today I had it happen on our dev server while using Chrome. I had logged into the application, allowed the application to session timeout, and then attempted to login again. The attempted login then failed to set the cookie. I remotely attached Visual Studio to the process on the server to begin debugging. The entire time I could step through my code, even deploy new code versions to the server with updates, restart the app, restart IIS on the server, attach and reattach to the project, and the issue persisted in Chrome. In Firefox, I was able to authenticate without issue.
From Chrome, the login would validate, attempt to set a Response Cookie as outlined above. Prior to redirect, I could see the properly set Response Cookie, as well as its counterpart in the Request Cookies. However, on each redirect after a seemingly successful login, the Response and Request Cookie are gone.
I enabled Trace on the application to view the cookie collection:
There is a .WebAuth in the Request Cookies Collection, as well as ASP.NET_SessionId and several ASPSESSIONIDxxxxxxxx, but when the page loads, only the ASP.NET_SessionId and ASPSESSIONIDxxxxxxxx cookies are available in the Request.Cookies scope, no sign of the .WebAuth. However, in the page's Trace information after render, there multiple .WebAuth cookies listed, it is just that the page seems to have no access to them.
Primarily, on a working version after authentication there is both a .WebAuth Response and Request Cookie in the page's Trace info. But on a non functioning browser window, the Response Cookie is absent.
Has anyone else had any experience with this? It is such a nagging issue, and so sporadic, but I would love to be able to resolve it. My concern is that it may be affecting users and we would have no knowledge since the description of the issue is so convoluted.
Based on your scenario you may be running into browser's restrictions on number of cookies per domain/total. The limits are relatively high, but exist (spec: http://www.ietf.org/rfc/rfc2109.txt, section 6.3 , some recent information - http://www.nczonline.net/blog/2008/05/17/browser-cookie-restrictions/)
If you get it happening again try to look at actual server responses (i.e. using Fiddler) to see if cookies are send to the browser. Check what cookies are set for the domain and current page (depending on browser there are different ways of doing it, in all browsers you can see some cookies by running following in address bar javascript:alert(document.cookie) )
This is a non-persistent cookie issue. Session simply times out.
Try changing false to true in this line:
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, Username, DateTime.Now, DateTime.Now.AddMinutes(SessionTimeout), true, Username);
Also add
faCookie.Expires = DateTime.Now.AddMinutes(SessionTimeout);
I have fall in similar situation, things was certain as you described. But, later the reason have been found.
ASP.NET and IIS understood MyApplication and myapplication as one equal, but browsers undertood them as different. So, when we set auth cookies for /MyApplication, they were not sent to the server, when we go to /myapplication.
The fix is next:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string url = HttpContext.Current.Request.Url.PathAndQuery;
string application = HttpContext.Current.Request.ApplicationPath;
if (!url.StartsWith(application))
{
HttpContext.Current.Response.Redirect(application + url.Substring(application.Length));
Response.End();
return;
}
}
I have multiple subdomains trying to use a single subdomain for authentiction using forms authentication all running on windows server 2008 r2.
All of the forms authentication pages are setup to use the same name, and on the authentication page the cookie is added with the following snippet:
FormsAuthentication.SetAuthCookie(txtUserName.Text, false);
System.Web.HttpCookie MyCookie = System.Web.Security.FormsAuthentication.GetAuthCookie(User.Identity.Name.ToString(), false);
MyCookie.Domain = ConfigurationManager.AppSettings["domainName"];
Response.AppendCookie(MyCookie);
When I am logged in to signon.mysite.com the page.user.identity.isauthenticated and page.user.identity.name properties both work fine. When I navigate to subdomain.mysite.com the page.user.identity.isauthenticated returns true, bue the name is empty.
I tried to retrieve it from the cookie using the following, but it also was blank.
HttpCookie cookie = Request.Cookies[".ASPXAUTH"];
FormsAuthenticationTicket fat = FormsAuthentication.Decrypt(cookie.Value);
user2_lbl.Text = fat.Name;
When googling the issue I found some people saying something must be added to global.asax and other saying it wasn't necessary.
The goal is to be able to login on the authentication subdomain and have the user identity accessible from the root site and other subdomains. Machine keys match in all web.config, and the AppSettings["domainName"] is set to "mysite.com" currently.
Does anyone know what is preventing me from accessing the user information?
Change "mysite.com" to be ".mysite.com" (note the leading ".").
That'll tell the browser that the cookie is valid for subdomains as well.