I have an MVC 4 website where a user can login and I save a cookie with their session information so they don't have to login again.
public void SetCookie(HttpCookie cookie)
{
HttpContext.Current.Response.Cookies.Set(cookie);
}
This works on desktop devices, but when I run this on iOS, it doesn't work 100% of the time. If I leave at least 1 page open in the mobile browser (either Chrome or Safari), and navigate back to my site, my session cookie is found and I don't have to login. But if I close ALL windows of that browser, then the next time I navigate back to my site, the session cookie is not found. I'm setting a 1 year duration/expiration on my cookie so it's not expiring.
The only thing I've found so far is that this is a bug in .NET 4.0 and is fixed in .NET 4.5. I believe I'm already running .NET 4.5, by looking at my *.csproj and the TargetFrameworkVersion element:
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{xxxxxxxxxxxxxxxxxx}</ProjectGuid>
<ProjectTypeGuids>{xxxxxxxx};{xxxxxxxxxxxxx};{xxxxxxxxxxxxxxxx}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MyApp</RootNamespace>
<AssemblyName>MyApp</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<UseIISExpress>true</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
</PropertyGroup>
Is this a bug with .NET (or MVC), or is it my coding? Do I need to upgrade to MVC 5 (instead of the original recommendation of upgrading to .NET 4.5)?
This has really been bugging me since the majority of the traffic to my site in the next coming months will be from mobile users, and I'll probably lose some of them if they have to keep logging in everytime (I know I hate having to login on a mobile device...)
Edit:
Btw - Here's another page on the same subject: Asp.Net Forms Authentication when using iPhone UIWebView
And I've also tried implementing this and it didn't work either:
http://www.bloggersworld.com/index.php/asp-net-forms-authentication-iphone-cookies/
And the above includes Scott Hanselmans suggested fix:
http://www.hanselman.com/blog/FormsAuthenticationOnASPNETSitesWithTheGoogleChromeBrowserOnIOS.aspx
Here's my cookie creation code, in case it helps:
private void CreateAndSetAuthenticationCookie(int loginId, string username)
{
HttpCookie cookie = CreateAuthenticationCookie(loginId, username);
_cookieService.SetCookie(cookie);
}
private HttpCookie CreateAuthenticationCookie(int loginId, string username)
{
string userData = string.Format("loginId:{0},username:{1}", loginId, username);
var ticket = new FormsAuthenticationTicket(loginId, username, DateTime.Now, DateTime.Now.AddYears(1), false, userData, FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
return new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
}
One thing that I pointed out in my comments, I have "PersistentCookie" set to false....not sure if that makes a difference, I'll do more research.
Update:
First of all, the PersistentCookie is set to true now, and that did not have any changes on the behavior.
One of the commentors below suggested that I run Fiddler while accessing the website from iPhone. After going through the very short and easy steps to get that setup, here's what I found out.
The first request I tried pointed out (I believe) what the actual problem is. When I examined that first request, iOS Safari is sending a DNT header. Having no idea what that was, I looked it up and it's a "Do Not Track" header.
Next, I went and checked my Safari settings to turn that off. Guess what, it's already off. Why the hell is Safari (iOS) sending a DNT header when the setting (Settings -> Safari -> Do Not Track) is not set? Also, the Block Cookies, which is right below it, is set to "Never".
After getting frustrated with that, I went to check on Chrome for iOS to see if that still doesn't work. IT WORKS! From what I can tell, I'd close all tabs, try again, close all tabs, then kill the browser, and it still works. Hooray!
Now, I need to figure out why iOS Safari is sending the DNT header...
According to Apple's John Wilander, the WebKit engineer behind the feature, Safari now blocks all third-party cookies. That means that, by default, no advertiser or website is able to follow you around the internet using the commonplace tracking technology.
Go to Safari > Preferences > Websites, then click Page Zoom. You'll see all of your currently open pages and tabs, and you can click on the popup menu to choose a zoom level, which will be remembered for each site.
This is HORRIBLY embarrasing. I found out that I've had my iOS Safari browser in "Private Browsing Mode" FOR YEARS!
I feel like my "Software Developer" job title needs removed for awhile. Sorry for wasting everyone's time.
I think you answered your own question with persistent cookie. Regular cookies expire when the browser session ends (this is typically closing the browser). Persistent cookies have a date when they should be removed and can live across browser sessions.
Your code should look something like this:
private HttpCookie CreateAuthenticationCookie(int loginId, string username)
{
string userData = string.Format("loginId:{0},username:{1}", loginId, username);
var ticket = new FormsAuthenticationTicket(loginId, username,
DateTime.Now, DateTime.Now.AddYears(1),
false, userData, FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
return new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket){{
Expires = DateTime.Now.AddYears(1);
}};
}
This way your cookie will persist across browser sessions for one year.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With