Forms authentication cookie handling question (C#)


M

musosdev

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"].ToString();
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT AccountID FROM Accounts WHERE
[email protected] " +
"AND [email protected]"; // (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.Password, "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();
}

and here is the AuthenticateRequest function from global.asax...

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];

if (null == authCookie)
{
// There is no authentication cookie.
return;
}

FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
// Log exception details (omitted for simplicity)
return;
}

if (null == authTicket)
{
// Cookie failed to decrypt.
return;
}

// When the ticket was created, the UserData property was assigned a
// pipe delimited string of role names.
string[] roles = authTicket.UserData.Split(new char[] { '|' });


// Create an Identity object
FormsIdentity id = new FormsIdentity(authTicket);

// This principal will flow throughout the request.
System.Security.Principal.GenericPrincipal principal = new
System.Security.Principal.GenericPrincipal(id, roles);
// Attach the new principal object to the current HttpContext object
Context.User = principal;
}
Can someone take a look and point out what I'm doing wrong?!!? I'm new to
Forms auth!

Thanks,


Dan
 
Ad

Advertisements

M

musosdev

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 said:
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"].ToString();
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT AccountID FROM Accounts WHERE
[email protected] " +
"AND [email protected]"; // (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.Password, "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();
}

and here is the AuthenticateRequest function from global.asax...

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];

if (null == authCookie)
{
// There is no authentication cookie.
return;
}

FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
// Log exception details (omitted for simplicity)
return;
}

if (null == authTicket)
{
// Cookie failed to decrypt.
return;
}

// When the ticket was created, the UserData property was assigned a
// pipe delimited string of role names.
string[] roles = authTicket.UserData.Split(new char[] { '|' });


// Create an Identity object
FormsIdentity id = new FormsIdentity(authTicket);

// This principal will flow throughout the request.
System.Security.Principal.GenericPrincipal principal = new
System.Security.Principal.GenericPrincipal(id, roles);
// Attach the new principal object to the current HttpContext object
Context.User = principal;
}
Can someone take a look and point out what I'm doing wrong?!!? I'm new to
Forms auth!

Thanks,


Dan
 
S

Steven Cheng

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:
(e-mail address removed).

==================================================
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=?= <[email protected]>
References: <[email protected]>
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 said:
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 protected] " +
"AND [email protected]"; // (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
, //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();
}

and here is the AuthenticateRequest function from global.asax...

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];

if (null == authCookie)
{
// There is no authentication cookie.
return;
}

FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
// Log exception details (omitted for simplicity)
return;
}

if (null == authTicket)
{
// Cookie failed to decrypt.
return;
}

// When the ticket was created, the UserData property was assigned a
// pipe delimited string of role names.
string[] roles = authTicket.UserData.Split(new char[] { '|' });


// Create an Identity object
FormsIdentity id = new FormsIdentity(authTicket);

// This principal will flow throughout the request.
System.Security.Principal.GenericPrincipal principal = new
System.Security.Principal.GenericPrincipal(id, roles);
// Attach the new principal object to the current HttpContext object
Context.User = principal;
}
Can someone take a look and point out what I'm doing wrong?!!? I'm new to
Forms auth!

Thanks,


Dan
 
M

musosdev

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 protected] " +
"AND [email protected]"; // (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" said:
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:
(e-mail address removed).

==================================================
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=?= <[email protected]>
References: <[email protected]>
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 said:
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 protected] " +
"AND [email protected]"; // (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
, //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();
}
 
Ad

Advertisements

S

Steven Cheng [MSFT]

Thanks for your reply Dan,

Glad that you've got progress. Yes, the code you posted looks correct. For
the new problem you mentioned, I think it is an unexpected behavior because
the following two statements should be enough for the logout task:

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

==========

whats's the "/mymusos/" path pointing? A protected folder? You can try
simply redirect to a protected page(maybe the default page of site) to see
whether it redirects to login page.

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:
(e-mail address removed).

This posting is provided "AS IS" with no warranties, and confers no rights.


--------------------
From: =?Utf-8?B?bXVzb3NkZXY=?= <[email protected]>
References: <[email protected]>
Subject: RE: Forms authentication cookie handling question (C#)
Date: Thu, 27 Mar 2008 09:49:00 -0700
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"].ToSt ring();
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT AccountID FROM Accounts WHERE
[email protected] " +
"AND [email protected]"; // (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.Passwor d,
"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" said:
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:
(e-mail address removed).

==================================================
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=?= <[email protected]>
References: <[email protected]>
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

:

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 protected] " +
"AND [email protected]"; // (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
,
//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();
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top