Forms Authentication and authentication cookie not persisting
aware that there are a lot of questions relating to Forms Authentication and the persistence of cookies, but having spent most of a day delving around, I'm still having difficulties.
My Problem
I am working on a web app (VS2010 but webapp is f/w 3.5) which restricts access to certain parts of the app to authenticated users (whereas other parts are open). My problem is that my authentication cookies do not appear to be persisting after I close the browser.
My Approach
I have written a simple login.aspx page which is configured in web.config as follows:
<authentication mode="Forms">
...and the individual pages' behaviour are declared like so:
<location path="ClientManageAccount.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
...which works fine in every respect EXCEPT for these cookie shenanigans...
I create the authentication cookie manually once I have authenticated my user's login & password against the database (which works fine) in the login.aspx page. If the user selects the 'keep me logged in' checkbox, the cookie is generated using this method:
private void GenerateAuthenticationCookie(int expiryInMinutes, Guid userGuid)
{
DateTime cookieExpiration = DateTime.Now.AddMinutes(expiryInMinutes); // change to months for production
var authenticationTicket =
new FormsAuthenticationTicket(
2,
userGuid.ToString(),
DateTime.Now,
cookieExpiration,
true,
string.Empty,
FormsAuthentication.FormsCookiePath);
// ticket must be encrypted
string encryptedTicket = FormsAuthentication.Encrypt(authenticationTicket);
// create cookie to contain encrypted auth ticket
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.Expires = authenticationTicket.Expiration;
authCookie.Path = FormsAuthentication.FormsCookiePath;
// clear out existing cookie for good measure (probably overkill) then add
HttpContext.Current.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
HttpContext.Current.Response.Cookies.Add(authCookie);
}
The objective here is that I store a user Guid in the auth cookie, which I will then use to restore a user object into session (this is also in the login.aspx page, and my thinking is that I'd like to pluck the user guid from the auth cookie that I have created, and use it to stuff the corresponding user record into session and redirect to the requested page):
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
TryAutoLogin();
}
}
private void TryAutoLogin()
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
if (cookie != null)
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
if (ticket != null)
{
if (ticket.Name.Length > 0)
{
try
{
Guid userGuid = new Guid(ticket.Name);
KUser user = UserFunctions.GetUserFromUserGuid(userGuid);
if (user != null) Session["User"] = user;
FormsAuthentication.RedirectFromLoginPage(userGuid.ToString(), true);
}
catch (Exception anyException)
{
// don't do anything for now - do something smart later :-) }
}
}
}
}
Finally, here is the code for the login button on my login.aspx page:
protected void Submit_OnClick(object sender, EventArgs e)
{
long userId = 0;
UserAuthenticationStatus status;
status = (UserAuthenticationStatus)UserFunctions.UserAuthenticates(EmailAddress.Text, Password.Text, ref userId);
switch (status)
{
case UserAuthenticationStatus.Authenticated:
//email address and password match, account ok, so log this user in
KUser user = UserFunctions.GetUser(userId);
Session["User"] = user;
if (ChkRememberMe.Checked)
{
GenerateAuthenticationCookie(15, user.UserGuid); // 15 minutes
FormsAuthentication.RedirectFromLoginPage(user.UserGuid.ToString(), true);
}
else
{
FormsAuthentication.RedirectFromLoginPage(user.UserGuid.ToString(), false);
}
break;
case UserAuthenticationStatus.AuthButLocked:
// email/pwd match but account is locked so do something
ShowLockedAccountMessage();
break;
case UserAuthenticationStatus.EmailFoundIncorrectPassword:
case UserAuthenticationStatus.EmailNotFound:
case UserAuthenticationStatus.Unknown:
// either the email wasn't found, or the password was incorrect or there was some other problem
// present message stating this and offer chance to register
ShowFailedLoginMessage();
break;
default:
ShowUnavailableMessage();
break;
}
}
As you can see, there's nothing particularly complex going on, but despite the fact that the authCookie which is created in GenerateAuthenticationCookie(..) being created correctly (as far as I can tell) it does not persist.
One thing I have noticed is that if I place some code into the Application_AuthenticateRequest method in global.asax.cs, such as:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
if (cookie != null)
{
int x = 4; // just a dummy line so that I can set a breakpoint
}
}
...that breakpoint is sometimes hit following a new browser session, although it stops being hit once I navigate away from the startup page (in this case a dummy start.aspx page used purely for dev & testing).
So, apologies for the long question, but I'm truly suffering here.
Things I have checked/tried
Ensuring that the code is being executed - YES Browser settings - i.e. no deletion of cookies on exit - CONFIRMED NO DELETION Trying different timeouts - e.g. equal or different to the web.config timeout, doesn't seem to matter... ...and of course at least twenty or thirty different previous questions, but to no avail.
System/Dev Environment
Windows 7 64-bit, VS2开发者_C百科010 (but proj is a 3.5), SQL Server 2008 Express.
On my dev server, this problem remains so I'm not sure it's necessarily environmental - that machine is a WS2008R2 box running SQL 2008R2 - and the same problem remains.
Does anyone, anywhere, have any ideas for things I can try here? I have a hunch I could get this working by intercepting the initial Application_AuthenticateRequest hit in global.asax.cs and stuffing something into session state to mark as 'authenticated' (to avoid an expensive authentication attempt every time that method is called, which turns out to be several times per page.
Thanks in advance,
John
OK, having spent all that time writing that, I had a moment of clarity and realised that (a) I didn't need to be doing any of that checking on the Page_Load() as (if this were working properly) the login.aspx page wouldn't be called at all, and (b) I ought to have been able to get to the cookie from the Session_Start - which is where I relocated the TryAutoLogin code.
This in itself was a step forward, but despite retrieving the cookie and therefore the user guid from it, I found that by I was still getting punted back to the login.aspx page.
It was at this point I recalled the fact that I have a parent master page and two child master pages - one which I set for non-authentication pages (e.g. homepage) and one for those pages requiring authentication. I vaguely recalled a problem with session timeouts and had placed the following code in the OnInit override:
if (Session["User"] == null)
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
Response.End();
}
...which in itself wasn't so bad (and avoided a nasty bug on timeouts) but also on the start.aspx page, I found this gem:
Session.Clear();
...in the Page_Load!
So, what was happening was that I was inadvertently clearing the session into which I had placed my newly recovered user record. Which meant that the authorisation master page's OnInit override was then detecting the absence of the user object and - ta dah! - signing the user out, which in turn removes the authorisation cookie...
So, a bit of wiring and some sleuthing later, and I can put this one to bed.
Thanks for reading (even if I did figure it out on my own)... :)
精彩评论