RE: Forms authentication cookie handling question (C#)



Steven,

Thanks for the help. That's definitely better. Here's what I've done...

I took your advice and moved my authentication control to Login_LoggingIn(),
adding the e.Cancel = true line at the bottom;

I also replaced all of my ticket authentication code with the
FormsAuthentication.SetCookie() command. Here's the code now...

protected void loginMyMusos_LoggingIn(object sender, LoginCancelEventArgs e)
{
FormsAuthentication.Initialize();

SqlConnection conn = new SqlConnection();
conn.ConnectionString =
ConfigurationManager.ConnectionStrings["musoswireDBConnectionString1"].ToString();
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT AccountID FROM Accounts WHERE
Email=@username " +
"AND Password=@password"; // (this should really be a stored
procedure, shown here for simplicity)

cmd.Parameters.Add("@username", SqlDbType.NVarChar, 64).Value =
loginMyMusos.UserName;
cmd.Parameters.Add("@password", SqlDbType.NVarChar, 64).Value =
FormsAuthentication.HashPasswordForStoringInConfigFile(loginMyMusos.Password,
"sha1");
(loginMyMusos.Password, "sha1");

SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
FormsAuthentication.SetAuthCookie(loginMyMusos.UserName,
loginMyMusos.RememberMeSet);

string returnUrl = Request.QueryString["ReturnUrl"];
if (returnUrl == null) returnUrl = "/mymusos/acc/default.aspx";

Response.Redirect(returnUrl);
}
else
{
// Username and or password not found in our database...
loginMyMusos.FailureText = "Username / password incorrect.
Please login again.";
}
reader.Close();
conn.Close();
cmd.Dispose();

e.Cancel = true;
}

First of all, does that look right? :)

From my point of view it seems to work ok. I can login and out, I can close
the browser and it won't let me to those pages UNLESS ive login again. If I
tick the "remember me" checkbox, I can close the browser, and get to the
other pages without logging in.

The only issue is that if I "remember me" and close the browser, then return
to the authenticated section, and log out, if I go to the authenticated
section I can still get in. If i then close the browser and open again, I do
have to login.

Is that just a symptom of the way things work? I know from experience that
many sites say "make sure you close the browser to completely log out".

So my question here .. is this expected behaviour, or is my signout code
wrong?

Here's the signout code...

{
FormsAuthentication.SignOut();
Response.Redirect("/mymusos/", true);
}

Is there command I need to use to delete the cookie immediately?!

Thanks for all the help,


Dan



""Steven Cheng"" wrote:

Hi Dan,

Based on your description, you're encountering some problem when
programmatically generate forms authentication ticket and set it in ASP.NET
Login control's event, correct?

I've checked the code and configuration schema you provided. Here are
something I found that maybe the cause of the problem:

** the configuration schema should be ok as I haven't found any particular
problem in it.

** For the code part, there are two things we may take care here:

1) You use the Login control's "Authentication" event to do the user
validation and set custom Forms authentication cookie/ticket there. I think
this is not the correct place since "Authentication" is only used for you
to provide custom user validation code logic, it will still use the
LoginControl's default code logic to generate authentication cookie. The
reasonable approach (if you want to generate t he authentication cookie
yourself ) is as below:

You can use the Login Control's "LoggingIn" event to do the custom ticket
generating and validating steps and you need to cancel the event there(so
as to prevent the built-in cookie generating process continue and use your
own cookie/ticket). e.g.

===========
protected void Login1_LoggingIn(object sender, LoginCancelEventArgs e)
{
//put your authentication and ticket generate code here

e.Cancel = true;
}
==========

2) For the "remember me" setting, if you do not generate the authentication
ticket & cookie your self, you can simply call
"FormsAuthentication.SetAuthCookie(... , true) and set the second paramete
to true. However, since you need to manually take care of this. yes, the
FormsAuthenticationTicket class has a "createPersistentCookie" parameter in
constructor, however, setting this is not enough. You also need to set the
"Expire" of the HttpCookie you add into response. Here is the code I picked
from the asp.net built-in code(from reflector):


==========================================
private static HttpCookie GetAuthCookie(string userName, bool
createPersistentCookie, string strCookiePath, bool hexEncodedTicket)
{
Initialize();
if (userName == null)
{
userName = string.Empty;
}
if ((strCookiePath == null) || (strCookiePath.Length < 1))
{
strCookiePath = FormsCookiePath;
}
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2,
userName, DateTime.Now, DateTime.Now.AddMinutes((double) _Timeout),
createPersistentCookie, string.Empty, strCookiePath);
string str = Encrypt(ticket, hexEncodedTicket);
if ((str == null) || (str.Length < 1))
{
throw new
HttpException(SR.GetString("Unable_to_encrypt_cookie_ticket"));
}
HttpCookie cookie = new HttpCookie(FormsCookieName, str);
cookie.HttpOnly = true;
cookie.Path = strCookiePath;
cookie.Secure = _RequireSSL;
if (_CookieDomain != null)
{
cookie.Domain = _CookieDomain;
}
if (ticket.IsPersistent)
{
cookie.Expires = ticket.Expiration;
}
return cookie;
}
=============================

As you can see, it add the following code to ensure the persistent of the
cookie:

if (ticket.IsPersistent)
{
cookie.Expires = ticket.Expiration;
}

Here are some other good articles explain how the ASP.NET
formsauthentication works:


#Explained: Forms Authentication in ASP.NET 2.0
http://msdn2.microsoft.com/en-us/library/aa480476.aspx

#Understanding the Forms Authentication Ticket and Cookie
http://support.microsoft.com/kb/910443

Hope this helps.

Sincerely,

Steven Cheng

Microsoft MSDN Online Support Lead


Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
msdnmg@xxxxxxxxxxxxxx

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.




--------------------
From: =?Utf-8?B?bXVzb3NkZXY=?= <musoswire@xxxxxxxxxxxxxxxx>
References: <74986539-74D9-42F4-B8FD-144F8BAB01FE@xxxxxxxxxxxxx>
Subject: RE: Forms authentication cookie handling question (C#)
Date: Sat, 22 Mar 2008 15:50:00 -0700


Also relevant are the web.config elements, which I've pasted below...

<authentication mode="Forms">
<forms loginUrl="/mymusos/Default.aspx"
protection="All"
timeout="30"
name=".ASPXAUTH"
path="/mymusos/account/"
requireSSL="false"
slidingExpiration="true"
defaultUrl="/mymusos/account/Default.aspx"
cookieless="UseDeviceProfile"
enableCrossAppRedirects="false" />

</authentication>

...and...

<location path="mymusos/account">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>


Thanks,



Dan

"musosdev" wrote:

Hi everyone,

I'm creating some Forms authentication for a section of my website. The
site
is for musicians, and each band or musician has an "account". All i want
to
do is prevent them form accessing the account manager without logging in.

I've got that working with the <location> element of web.config,
<authentication> section and a Login control. I'm using SHA1 to store
the
password, and passing the hashed password to check against the database.

I think I've even got cookie storage working, although the site never
lets
me be "remembered" even when I tick the box.

I've stored the email address of the user in the UserData element of the
authentication ticket, as I don't need roles. But I'm unsure if this is
right
(what should I store here If I dont need roles), and think that might be
why
it's not working...

Here's the Login Button code...

protected void loginMyMusos_Authenticate(object sender,
AuthenticateEventArgs e)
{
// Initialize FormsAuthentication (reads the configuration and
gets
// the cookie values and encryption keys for the given
application)
FormsAuthentication.Initialize();

// Create connection and command objects
SqlConnection conn = new SqlConnection();
conn.ConnectionString =

ConfigurationManager.ConnectionStrings["musoswireDBConnectionString1"].ToStr
ing();
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT AccountID FROM Accounts WHERE
Email=@username " +
"AND Password=@password"; // (this should really be a stored
procedure, shown here for simplicity)

// Fill our parameters
cmd.Parameters.Add("@username", SqlDbType.NVarChar, 64).Value =
loginMyMusos.UserName;
cmd.Parameters.Add("@password", SqlDbType.NVarChar, 64).Value =

FormsAuthentication.HashPasswordForStoringInConfigFile(loginMyMusos.Password
,
"sha1");


//FormsAuthentication.HashPasswordForStoringInConfigFile(loginMyMusos.Passwo
rd, "sha1");
// you can use the above method for encrypting passwords to be
stored in the database
//Response.Write(cmd.Parameters["@password"].ToString());
// Execute the command

SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
// Create a new ticket used for authentication
FormsAuthenticationTicket ticket = new
FormsAuthenticationTicket(
1, // Ticket version
loginMyMusos.UserName, // Username to be associated with
this
ticket
DateTime.Now, // Date/time issued
DateTime.Now.AddHours(1), // Date/time to expire
loginMyMusos.RememberMeSet, // "true" for a persistent user
cookie (could be a checkbox on form)
reader[0].ToString(), // User-data (the roles from this user
record in our database)
FormsAuthentication.FormsCookiePath); // Path cookie is
valid for

// Hash the cookie for transport over the wire
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie
(it's the name specified in web.config)
hash); // Hashed ticket

// Add the cookie to the list for outbound response
Response.Cookies.Add(cookie);

// Redirect to requested URL, or homepage if no previous
page
requested
string returnUrl = Request.QueryString["ReturnUrl"];
if (returnUrl == null) returnUrl =
"/mymusos/account/default.aspx";

// Don't call the FormsAuthentication.RedirectFromLoginPage
here, since it could
// replace the custom authentication ticket we just added...
Response.Redirect(returnUrl);
}
else
{
// Username and or password not found in our database...
loginMyMusos.FailureText = "Username / password incorrect.
Please login again.";
}
// (normally you'd put all this db stuff in a try / catch /
finally
block)
reader.Close();
conn.Close();
cmd.Dispose();
}
.