Why did ASP.NET generate the same cookie key for a domain and subdomain? - asp.net

Bug:
I've got an ASP.NET web application that occasionally sets identical cookie keys for ".www.mydomain.com" and "www.mydomain.com". I'm trying to figure out what default cookie domain ASP.NET sets, and how I accidentally coded the site to sometimes prepend a "." to the cookie domain.
When 2 cookies have the same key and are sent up from the browser, the ASP.NET web application is unable to differentiate between the two because the domain value is not sent in the header. (See my previous question)
Evidence:
I've enabled W3C logging on the web server and verified that both cookies are sent from the client. Here's an example from the log file (paired down for brevity).
80 GET /default.aspx page= 200 0 0 - - - - - +MyCookie2=sessionID=559ddb9b-0f38-4878-bb07-834c2ca9caae;+MyCookie2=sessionID=e13d83cd-eac2-46fc-b39d-01826b91cb2c;
Possible Factor:
I am using subdomain enabled forms authentication.
Here's my web.config settings:
<authentication mode="Forms">
<forms domain="mydomain.com" enableCrossAppRedirects="true" loginUrl="/login" requireSSL="false" timeout="5259600" />
</authentication>
Here's and example of setting custom cookies:
HttpCookie cookie1 = new HttpCookie("MyCookie1") {HttpOnly = true, Expires = expiration};
logosCookie["email"] = user.Email;
logosCookie["keycode"] = user.PasswordHash;
logosCookie["version"] = currentCookieVersion;
context.Response.Cookies.Remove("cookie1");
context.Response.Cookies.Add(cookie1);
// set FormsAuth cookie manually so we can add the UserId to the ticket UserData
var userData = "UserId=" + user.UserID;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2, user.Email, now, expiration, true, userData);
string str = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, str)
{
HttpOnly = true,
Path = FormsAuthentication.FormsCookiePath,
Secure = FormsAuthentication.RequireSSL,
Expires = ticket.Expiration
};
if (FormsAuthentication.CookieDomain != null)
{
cookie.Domain = FormsAuthentication.CookieDomain;
}
context.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
context.Response.Cookies.Add(cookie1 );
Here's another example of setting a cookie.
var cookie2 = new HttpCookie("MyCookie2");
cookie2[CookieSessionIdKey] = Guid.NewGuid();
cookie2.Expires = DateTime.Now.AddYears(10);
HttpContext.Current.Response.Cookies.Set(cookie2);
Undesirable Resolution:
I can manually force the cookie domain to be a specific value, but I'd like to avoid explicitly declaring the domain. I'd prefer to use the default framework behavior and change my use of ASP.NET to avoid prepend the "." to the cookie domain for custom cookies.

When no domain is explicitly set by the server on the response, the browser is free to assign the cookie domain value. I haven't figured out exactly what conditions result in the browser setting "www.mydomain.com" vs ".mydomain.com" on a cookie domain when no domain is provided on the response, but it happened.
I have a feeling it's a result of explicitly setting the .ASPAUTH cookie domain value to ".mydomain.com" to enable cross subdomain authentication, while leaving other custom cookie domains set to the default (empty string, or "").
I'm going to go with the undesired solution, and explicitly set the cookie domain for all custom cookies to avoid browser quirks.

Related

ASP.Net Cannot set cookie with SameSite=None

I'm using MVC5 on IIS with .NET Framework 4.8.
I have the latest quality rollup installed(1) which is supposed to resolve some issues with samesite cookies.
I create three cookies as follows:
var now = DateTime.Now.ToLongTimeString();
var expiry = now.AddSeconds(30);
var cookieSameSiteNone = new HttpCookie("My.SameSite.None", $"sameSite None [{now}]")
{
Secure = true,
SameSite = SameSiteMode.None,
Expires = expiry
};
var cookieSameSiteLax = new HttpCookie("My.SameSite.Lax", $"sameSite Lax [{now}]")
{
Secure = true,
SameSite = SameSiteMode.Lax,
Expires = expiry
};
var cookieSameSiteStrict = new HttpCookie("My.SameSite.Strict", $"sameSite Strict [{now}]")
{
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = expiry
};
Response.Cookies.Add(cookieSameSiteStrict);
Response.Cookies.Add(cookieSameSiteLax);
Response.Cookies.Add(cookieSameSiteNone);
These cookies are set in the Application_EndRequest of the Global.asax. The application also uses OWIN for authentication.
Using FireFox (v72.0.2) I get the following cookies:
Note the cookie where SameSite was set to None has been received as "Unset"
Any idea how to set a cookie with SameSite=None?
I have seen other SO questions that suggest applyin patches to the .NET Framework(2), but I already have these installed
(1) https://support.microsoft.com/en-gb/help/4534132/kb4534132-cumulative-update-for-net-framework
(2) How to set SameSite cookie attribute to explicit None ASP NET Core
This may be an issue with FireFox's display of Cookies. Chrome does not appear to suffer with the same problem.
I've logged a bug with Mozilla(1).
(1) https://bugzilla.mozilla.org/show_bug.cgi?id=1613622

Forms authentication and authentication ticket cookie domain

I'm trying to configure DEV environment to support sub-domains with sharing authentication and session between them.
Currently, I configured IIS and hosts file on DEV machine to handle requests for mydomain, sd1.mydomain, sd2.mydomain, sd3.mydomain. Web application itself working as expected, I can browse all pages on all sub-domains, except the pages that requires authentication. When I try to log in, everything looks perfect on server side (user found, cookie created and added to response), but the cookie not arrives to browser (I tried Chrome and IE).
I have a code that creates and stores authentication ticket and I set domain=".mydomain" in authentication.forms in web.config:
var now = DateTime.UtcNow.ToLocalTime();
var ticket = new FormsAuthenticationTicket(
1 /*version*/, _user.Username, now, now.Add(FormsAuthentication.Timeout),
isPersistentCookie, _user.Username, FormsAuthentication.FormsCookiePath);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
if (ticket.IsPersistent)
{
cookie.Expires = ticket.Expiration;
}
cookie.Secure = FormsAuthentication.RequireSSL;
cookie.Path = FormsAuthentication.FormsCookiePath;
if (FormsAuthentication.CookieDomain != null)
{
cookie.Domain = FormsAuthentication.CookieDomain;
}
_httpContext.Response.Cookies.Add(cookie);
When I debug, the code above works fine, the user is correct and cookie with correct domain is added to response.
If I remove domain=".mydomain" from web.config, authentication works, but only on mydomain and not on sub-domains.
What I'm doing wrong?
Remove the dot on the beginning, from the domain=, you must have it as domain=".mydomain.com" with the first dot as stated here http://www.w3.org/Protocols/rfc2109/rfc2109 (page 7), thanks for the comment of #AlbatrossCafe
This setting is both on cookie and on authentication.
Nothing wrong. If the domain is not provided on a cookie, the cookie is supposed to work only for issuing domain.

Proper creation of a cross-domain forms authentication cookie

I'm just creating a simple test between two server. Basically if a user has already authenticated I want to be able to pass them between applications. I changed the keys to hide them
I have three questions:
What is the proper way to validate the cookie across domain application. For example, when the user lands at successpage.aspx what should I be checking for?
Is the below code valid for creating a cross domain authentication cookie?
Do I have my web.config setup properly?
My code:
if (authenticated == true)
{
//FormsAuthentication.SetAuthCookie(userName, false);
bool IsPersistent = true;
DateTime expirationDate = new DateTime();
if (IsPersistent)
expirationDate = DateTime.Now.AddYears(1);
else
expirationDate = DateTime.Now.AddMinutes(300);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
userAuthName,
DateTime.Now,
expirationDate,
IsPersistent,
userAuthName,
FormsAuthentication.FormsCookiePath);
string eth = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, eth);
if (IsPersistent)
cookie.Expires = ticket.Expiration;
cookie.Domain = ".myDomain.com";
Response.SetCookie(cookie);
Response.Cookies.Add(cookie);
Response.Redirect("successpage.aspx");
}
My config:
<authentication mode="Forms">
<forms loginUrl="~/Default.aspx" timeout="2880" name=".AUTHCOOKIE" domain="myDomain.com" cookieless="UseCookies" enableCrossAppRedirects="true"/>
</authentication>
<customErrors mode="Off" defaultRedirect="failure.aspx" />
<machineKey decryptionKey="#" validationKey="*" validation="SHA1" decryption="AES"/>
What is the proper way to validate the cookie across domain application.
For example, when the user lands at successpage.aspx what should I be checking for ?
There shouldn't be anything to check. Forms authentication mechanism will retrieve the ticket from the cookie, check if it is valid. If not present, or invalid, user will redirected to ~/Default.aspx .
This will work provided your cookie matches the configuration of your web.config
Is the below code valid for creating a cross domain authentication cookie ?
I think you shouldn't try to override the settings of your web.config by manually handling the cookie. I think there are better ways for handling cookie persistence (see below for web.config) and you are just implementing a part of the Forms authentication API (loosing web.config for SSL for example )
here, your manual cookie is not HttpOnly : you could for example be subject to cookie theft through XSS
FormsAuthentication has its own way of handling the cookie (see the TimeOut attribute description in http://msdn.microsoft.com/en-us/library/1d3t3c61%28v=vs.80%29.aspx) Your cookie persistence mechanism will be overwritten by this automatic behavior
Your code should just be :
if (authenticated)
{
bool isPersistent = whateverIwant;
FormsAuthentication.SetAuthCookie(userName, isPersistent );
Response.Redirect("successpage.aspx");
}
Do I have my web.config setup properly?
It should be ok for the domain attribute, as long as you want to share authentication among direct subdomains of mydomain.com (it won't work for x.y.mydomain.com), and mydomain.com is not in the public suffix list ( http://publicsuffix.org/list/ )
I would change the timeout and slidingExpiration attributes to :
<forms loginUrl="~/Default.aspx" timeout="525600" slidingExpiration="false" name=".AUTHCOOKIE" domain="myDomain.com" cookieless="UseCookies" enableCrossAppRedirects="true"/>
I guess it is a good way to handle the choice between one year persistent cookies and session cookies. See https://stackoverflow.com/a/3748723/1236044 for more info

Sharing ASP.NET cookies across sub-domains

I have two sites, both on the same domain, but with different sub-domains.
site1.mydomain.example
site2.mydomain.example
Once I'm authenticated on each, I look at the cookies included in subsequent request and they are identical for each site.
However, if I log into the first site, and then navigate to the other, I expect my cookie from site 1 to be sent with the request to site2, but this is not the case. Here are the properties of my cookies.
Logging into Site1, this cookie then exists
Name = MySite
Domain =
Has Keys = False
HttpOnly = False
Path = /
Value = 1C41854066B03D8CC5679EA92DE1EF427DAC65D1BA0E672899E27C57245C1F0B7E93AB01B5563363AB4815A8F4BDE9D293FD261E03F8E60B8497ABBA964D8D315CCE1C8DD220C7176E21DC361935CF6
Expires = 1/1/0001 12:00:00 AM
Logging into Site2, these cookies then exists.
Name = MySite
Domain =
Has Keys = False
HttpOnly = False
Path = /
Value = C8C69F87F993166C4D044D33F21ED96463D5E4EB41E1D986BF508DA0CBD5C2CA7D782F59F3BC96871108997E899FF7401C0D8615705BDB353B56C7E164D2302EE6731F41705016105AD99F4E0578ECD2
Expires = 1/1/0001 12:00:00 AM
I've set the domain on each (doesn't show up in a request cookie as it's only needed on the client).
I've made sure my Forms setting for each are identical
I've made sure my machine key settings are the same in both web configs.
I'm at a loss on why this isn't working. What is it that a cookie contains that the client will send it for one sub-domain and not the other when they are both using the same auth cookies so far as I can tell?
Please comment if there is more info you'd like to see. I've been struggling with this for two days now. According to this article this should be working.
code added
Here is my config file setting for my authentication. This is used in both sites.
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn"
defaultUrl="~/Home/Index"
name="MySite"
protection="All"
path="/"
domain="mydomain.example"
enableCrossAppRedirects="true"
timeout="2880"
/>
And here is my code to create the cookie in Site1.
//Add a cookie that the Site2 will use for Authentication
var cookie = FormsAuthentication.GetAuthCookie(userName, true);
cookie.Name = "MySite";
cookie.HttpOnly = false;
cookie.Expires = DateTime.Now.AddHours(24);
cookie.Domain = "mydomain.example";
HttpContext.Response.Cookies.Add(cookie);
HttpContext.Response.Redirect(site2Url,true);
UPDATE 2:
I noticed something strange while testing. When I add a cookie to the response for site1, it get's added to this directory...
C:\Users\jreddy\AppData\Roaming\Microsoft\Windows\Cookies
When I add a cookie to the response for site, it gets added to this directory...
C:\Users\jreddy\AppData\Roaming\Microsoft\Windows\Cookies\Low
That could be my problem. Could it be that one of my sites is included in the local intranet zone?
UPDATE 3: Problem found, solution unknown
It seems that my problem has to do with my second site being part of the Local Intranet Zone. If I go to Site1 using Firefox it works, but I have to enter my Windows credentials. If I go thru IE, my credentials are picked up automatically, but the cookies can't be read by site2. I may ask this in another question.
Set the property of Domain to .mydomain.example in each Cookies of two subdomains websites. Like:
Response.Cookies["test"].Value = "some value";
Response.Cookies["test"].Domain = ".mysite.example";
In Site A:
HttpCookie hc = new HttpCookie("strName", "value");
hc.Domain = ".mydomain.example"; // must start with "."
hc.Expires = DateTime.Now.AddMonths(3);
HttpContext.Current.Response.Cookies.Add(hc);
In Site B:
HttpContext.Current.Request.Cookies["strName"].Value
Add new cookie and specify domain like this
HttpCookie cookie = new HttpCookie("cookiename", "value");
cookie.Domain = "domain.example";
For forms authentication set this in web.config
<forms name=".ASPXAUTH"
loginUrl="login.aspx"
protection="All"
timeout="30"
path="/"
requireSSL="false"
domain="domain.example">
</forms>
The cookie will be accessible to all the subdomains.
In order for each domain to decrypt the the cookie, all web.config files must use the same encryption/decryption algorithm and key. (how to create a machine key)
Example:
// do not wrap these values like this in the web.config
// only wrapping for code visibility on SO
<machineKey
validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75
D7AD972A119482D15A4127461DB1DC347C1A63AE5F1CCFAACFF1B72A7F0A281
B"
decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719
F"
validation="SHA1"
decryption="AES"
/>
For easier deployments, these values can be stored in a separate file:
<machineKey configSource="machinekey.config"/>
For added security you can also encrypt the machine key for further protection..
If you're using Forms authentication on all of your sub domains, all you need to do is to add domain=".mydomain.example" property to the <forms> node in your web.config
Note the leading period in .mydomain.example
This simple change by itself will make your authentication cookie valid in all sub-domains; no need to manually set any cookies.
I've created a HttpContext extension method that will write a sub domain safe cookie.
Blog post and discussion
public static class HttpContextBaseExtenstions
{
public static void SetSubdomainSafeCookie(this HttpContextBase context, string name, string value)
{
var domain = String.Empty;
if (context.Request.IsLocal)
{
var domainSegments = context.Request.Url.Host.Split('.');
domain = "." + String.Join(".", domainSegments.Skip(1));
}
else
{
domain = context.Request.Url.Host;
}
var cookie = new HttpCookie(name, value)
{
Domain = domain
};
context.Response.SetCookie(cookie);
}
}
// usage
public class MyController : Controller
{
public ActionResult Index()
{
this.Context.SetSubdomainSafeCookie("id", Guid.NewGuid().ToString());
return View();
}
}

FormsAuthentication not working

I have a site that works as expected on my development box. That is, the formsauthentication ticket expires after 30 days. This is achieved through the following code
string roles = UserManager.getAuthenticationRoleString(txtUsername.Text);
HttpCookie formscookie = FormsAuthentication.GetAuthCookie(txtUsername.Text, true);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(formscookie.Value);
FormsAuthenticationTicket newticket = new FormsAuthenticationTicket(1, ticket.Name, DateTime.Now, DateTime.Now.AddDays(30), true, roles, ticket.CookiePath);
HttpCookie newCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(newticket));
newCookie.Expires = DateTime.Now.AddDays(30);
Response.Cookies.Add(newCookie);
I used fiddler to check that the expiration is set properly and I get this
.ASPXAUTH=84AB5430CF4B1C5F9B59C9285288B41F156FCAFA2A169EACE17A7778A392FA69F66770FD8A08FFD06064B00F0BD788FEEC4A5894B7089239D6288027170A642B3B7EB7DB4806F2EBBCF2A82EE20FD944A38D2FE253B9D3FD7EFA178307464AAB4BCB35181CD82F6697D5267DB3B62BAD; expires=Thu, 21-Jan-2010 18:33:20 GMT; path=/; HttpOnly
So I would expect it to expire in 30 days...But it only makes it about 30 minutes.
I have 3 other interesting tidbits about my environment / code
On the production box there are two sites pointing at the same code one for external access and one for internal access
When the I do get the login page because of premature expiration, the .ASPAUTH cookie is still there and sent to the browser
There is some role checking in the global.asax that looks like this
-
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
// Get the stored user-data, in this case, our roles
string userData = ticket.UserData;
string[] roles = userData.Split('|');
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
}
}
}
}
You'll need to add a machine key tag to web.config file. It's getting regenerated and that causes your premature timeout.
This is similar to the following question:
figuring out why asp.net authentication ticket is expiring
If the problem is that the user on the production box is kicked and has to log in again via FormsAuthentication, it's possible that the problem is IIS related and not .NET.
I've run into issues before where all the timeout settings in the world within the app didn't make a difference and the user was booted far too early.
Check your IIS settings for both the website and the application pool. There are settings in both related to timeouts, etc.
If II6:
Under website properties -> Home Directory tab -> Configuration Button -> Options Tab -> there is session state/length info here
Under application pool for your site -> Performance and Health tabs -> both have several settings that may recycle your pool (and essentially force a re-logon)
To debug, you could disable all health and performance checks on the pool, however be very careful as this could throttle your server if the app gets out of control.
You could also try putting the timeout settings in the web.config:
<system.web>
<authentication mode="Forms">
<forms timeout="XXXXX"/>
</authentication>
</system.web>
Anyway just some ideas from personal experience of similar issues. Hope it might help!

Resources