Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / ASP.NET / General / March 2008

Tip: Looking for answers? Try searching our database.

Forms authentication cookie handling question (C#)

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
musosdev - 22 Mar 2008 23:43 GMT
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=@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.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
musosdev - 22 Mar 2008 23:50 GMT
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,
>
[quoted text clipped - 148 lines]
>
> Dan
Steven Cheng - 24 Mar 2008 03:27 GMT
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@microsoft.com.

==================================================
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@community.nospam>
>References:  <74986539-74D9-42F4-B8FD-144F8BAB01FE@microsoft.com>
>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...
>
[quoted text clipped - 56 lines]
>>         SqlConnection conn = new SqlConnection();
>>         conn.ConnectionString =

ConfigurationManager.ConnectionStrings["musoswireDBConnectionString1"].ToStr
ing();
>>         conn.Open();
>>         SqlCommand cmd = conn.CreateCommand();
[quoted text clipped - 7 lines]
>> 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
[quoted text clipped - 102 lines]
>>
>> Dan
musosdev - 27 Mar 2008 17:49 GMT
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

> Hi Dan,
>
[quoted text clipped - 285 lines]
> >>         cmd.Dispose();
> >>     }
Steven Cheng [MSFT] - 28 Mar 2008 03:00 GMT
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:
msdnmg@microsoft.com.

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

--------------------
>From: =?Utf-8?B?bXVzb3NkZXY=?= <musoswire@community.nospam>
>References:  <74986539-74D9-42F4-B8FD-144F8BAB01FE@microsoft.com>
<39537479-3565-4660-AB9E-5020E2BDBE95@microsoft.com>
<9jCTlaVjIHA.360@TK2MSFTNGHUB02.phx.gbl>
>Subject: RE: Forms authentication cookie handling question (C#)
>Date: Thu, 27 Mar 2008 09:49:00 -0700

>Steven,
>
[quoted text clipped - 199 lines]
>> ==================================================
>> Get notification to my posts through email? Please refer to

http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
>> ications.
>>
[quoted text clipped - 86 lines]
>> >>         SqlConnection conn = new SqlConnection();
>> >>         conn.ConnectionString =

ConfigurationManager.ConnectionStrings["musoswireDBConnectionString1"].ToStr
>> ing();
>> >>         conn.Open();
[quoted text clipped - 8 lines]
>> >> 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
[quoted text clipped - 56 lines]
>> >>         cmd.Dispose();
>> >>     }

Rate this thread:







Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.