Getting little deeper: How ASP.Net Forms based authentication flows...
I have been recently supporting Asp.Net apart from IIS in the role of a Microsoft GTSC Developer Support Engineer.
I had been a programmer earlier, but had more expertise on C, C++ and other unmanaged non-web stuffs. I am new to Web technology as per the programming background is concerned. I started working on specific topics which we regularly encounter in our daily support calls, and found Forms based authentication to be one of the most interesting and challenging topics to troubleshoot.
Here i take a moment to dig deep in explaining the forms authentication. The below analysis helped me in a big way to understand how the HTTP traffice flows and what are the headers we need to concentrate upon.
I will show a series of Web request/response flows when a user tries to access a website which is configured for Forms based authentication.
Let's say, a user requests for accessing a protected web page on a site.
So accordingly he should be redirected to a login page where he needs to enter the credentials and once validated against a user data store, he should be taken to the requested page.
For our example I have written a very generic code as shown below:
Default.aspx page, which checks whether user is authenticated or not. If yes, then it displays webpage content. If user is not authenticated he will be redirected to the login page. You can copy paste and try it for yourself.
Default.aspx
<%@Page Language="VB" %>
<%@Import Namespace="System.Web.Security" %>
<script language="vb" runat="server">
Sub SignOut(objSender As Object, objArgs As EventArgs)
'delete the users auth cookie and sign out
FormsAuthentication.SignOut()
'redirect the user to their referring page
Response.Redirect(Request.UrlReferrer.ToString())
End Sub
Sub Page_Load()
'verify authentication
If User.Identity.IsAuthenticated Then
'display Credential information
displayCredentials.InnerHtml = "Current User : <b>" & User.Identity.Name & "</b>" & _
"<br><br>Authentication Used : <b>" & User.Identity.AuthenticationType & "</b>"
session("Name") = User.Identity.Name
Else
'Display Error Message
displayCredentials.InnerHtml = "Sorry, you have not been authenticated."
End If
End Sub
</script>
<html>
<head>
<title>Forms Authentication</title>
</head>
<body bgcolor="#FFFFFF" text="#000000">
<span class="Header">Forms Based Authentication using standard method</span>
<br>
<br>
<div id="displayCredentials" runat="server" />
<br>
<br>
<form runat="server" method="POST">
<asp:TextBox id="TextBox1" runat="server" />
<asp:Button id="cmdSignOut" text="Sign Out" runat="server" onClick="SignOut" />
<asp:Button id="Button1" runat="server" text="Submit"/><br>
<asp:TextBox id="TextBox2" runat="server" />
</form>
</body>
</html>
Login.aspx
<%@Page Language="VB" %>
<%@Import Namespace="System.Web.Security" %>
<script language="VB" runat="server">
Sub ProcessLogin(objSender As Object, objArgs As EventArgs)
If FormsAuthentication.Authenticate(txtUser.Text, txtPassword.Text) Then
FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked)
Else
ErrorMessage.InnerHtml = "<b>Something went wrong...</b> please re-enter your credentials..."
End If
End Sub
</script>
<html>
<head>
<title>Standard Forms Authentication Login Form</title>
</head>
<body bgcolor="#FFFFFF" text="#000000">
<form runat="server">
<table width="400" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="80">Username : </td>
<td width="10"> </td>
<td><asp:TextBox Id="txtUser" width="150" runat="server"/></td>
</tr>
<tr>
<td>Password : </td>
<td width="10"> </td>
<td><asp:TextBox Id="txtPassword" width="150" TextMode="Password" runat="server"/></td>
</tr>
<tr>
<tr>
<td></td>
<td width="10"> </td>
<td><asp:CheckBox id="chkPersistLogin" runat="server" />Remember my credentials<br>
</td>
</tr>
<tr>
<td> </td>
<td width="10"> </td>
<td><asp:Button Id="cmdLogin" OnClick="ProcessLogin" Text="Login" runat="server" /></td>
</tr>
</table>
<br>
<br>
<div id="ErrorMessage" runat="server" />
</form>
</body>
</html>
For demonstration purpose I have added users to the web.config files instead of using any other store like a SQL server or Active Directory store for storing user's credentials.
Here is the web.config file section of our interest:
<configuration>
<system.web>
<customErrors mode="Off"/>
<authentication mode="Forms">
<forms name="FormsAuthCookie" path="/" loginUrl="login.aspx" protection="All" timeout="1" slidingExpiration="false" >
<credentials passwordFormat="Clear">
<user name="john" password="test1" />
<user name="Randy" password="test2" />
....
</credentials>
</forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
<sessionState
mode="InProc"
stateConnectionString="tcpip=127.0.0.1:42424"
stateNetworkTimeout="10"
sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
sqlCommandTimeout="30"
cookieless="UseCookies"
cookieName="AppSessionCookie"
timeout="2" >
</sessionState>
</system.web>
</configuration>
Here if you notice, I have set the SlidingExpiration to False, which mean users will be logged out after a specific interval from the time they logged in, in our case it is set to timeout= "1" min.
If you want you can encrypt the user's credentials using hash algorithm like SHA1 etc. but it is not in the agenda of this blog.
I won't go much into the details about the settings here since you will get tonnes of articles on implementing forms based authentication on the net.
I will basically show you how Request/Response flow occurs between the server and the client during Forms based authentication.
Here we go:
Step 1: Client sends a web request for the Default.aspx (or any page of your choice in the website except the login page; who would prefer to go through a login page to access one's desired webpage if given a chance :-) ) page to the server.
[You can focus only on the bold headers for our purpose]
You type in the following url in the IE browser, https://saurabsi-sec/FormsAuthentication/default.aspx and hit Go!
From Client
GET /FormsAuthentication/default.aspx HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Accept-Language: en-us
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 1.1.4322; InfoPath.2)
Host: saurabsi-sec
Proxy-Connection: Keep-Alive
Step 2: Server sends a response back to the Client with a status 302 Object Moved:
From Server
HTTP/1.1 302 Found
Date: Thu, 19 Apr 2007 07:11:43 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
MicrosoftOfficeWebServer: 5.0_Pub
X-AspNet-Version: 2.0.50727
Location: /FormsAuthentication/login.aspx?ReturnUrl=%2fFormsAuthentication%2fdefault.aspx
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 196
Notice the Response status code and the Location in the response header. Server says to the client that it (client) needs to resend a request to the url mentioned in the Location header.
Step 3: Client then resends a new request for a /FormsAuthentication/login.aspx?ReturnUrl=%2fFormsAuthentication%2fdefault.aspx
page. Now here since the client has to first get to the login.aspx page it will send a GET request first and not a POST request. Remember the first request to any site will be a GET and not POST.
From Client
GET /FormsAuthentication/login.aspx?ReturnUrl=%2fFormsAuthentication%2fdefault.aspx HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Accept-Language: en-us
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 1.1.4322; InfoPath.2)
Host: saurabsi-sec
Proxy-Connection: Keep-Alive
Remember the querystring ReturnUrl shows the original requested page. Client is supposed to send a request later after authentication is done to this page. This is the way how the request/response maintains a track of the requested page throughout the transaction.
Step 4: Server sends back the requested login.aspx page with a 200 OK Response.
From Server
HTTP/1.1 200 OK
Date: Thu, 19 Apr 2007 07:11:43 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
MicrosoftOfficeWebServer: 5.0_Pub
X-AspNet-Version: 2.0.50727
Set-Cookie: AppSessionCookie=vtd2qg55mkcypqnn53obxm45; path=/; HttpOnly
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1427
Notice that Session ID gets created at this stage by the server and is sent along with the response to the client. From now onwards Server will keep track of a user's session using this cookie. Note the session ID gets created at this stage and not the authentication cookie. Remember Session key gets created the moment a successful transaction occurs like a 200 OK between the server and the client.
Step 5: Now the client has recieved the login.aspx page. It enters the credentials for username and password and sends it across to the server again. Notice that this is a POST request to the login.aspx page now and this time it also sends credentials like username and password. We are sending the username and password as part of the Body of the request and not as part of the header.
From Client
POST /FormsAuthentication/login.aspx?ReturnUrl=%2fFormsAuthentication%2fdefault.aspx HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Referer: https://saurabsi-sec/FormsAuthentication/login.aspx?ReturnUrl=%2fFormsAuthentication%2fdefault.aspx
Accept-Language: en-us
Content-Type: application/x-www-form-urlencoded
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 1.1.4322; InfoPath.2)
Proxy-Connection: Keep-Alive
Content-Length: 277
Host: saurabsi-sec
Pragma: no-cache
Cookie: AppSessionCookie=vtd2qg55mkcypqnn53obxm45
You have the option of sending it as a querystring too, in such a case it will form a part of the request header and not body.
If you check the Queystring here, it shows ReturnUrl=/FormsAuthentication/default.aspx
Also checking the forms body, we find:
txtUser=john
txtPassword=test1
cmdLogin=Login
Step 6: Server receives the credentials and then goes ahead and authenticates the user. Once the user has been authenticated server responds back with a 302 Found response ,asking the client to send another request for the originally requested page, i.e. Default.aspx. How it determines the original requested page as default.aspx? You are right, it's through the querystring that we just discussed above.
From Server
HTTP/1.1 302 Found
Date: Thu, 19 Apr 2007 07:11:50 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
MicrosoftOfficeWebServer: 5.0_Pub
X-AspNet-Version: 2.0.50727
Location: /FormsAuthentication/default.aspx
Set-Cookie: FormsAuthCookie=82A747623272A0A3A0C36EC6AD1FBA35A47592B1CFB38E54A1A7C5BC24ECAA2563715D4225EAE8927B98EC3DAD6FB9875E67FCA344AECCA19837A40B2311E373; path=/; HttpOnly
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1590
Notice the FormsAuthCookie here, this has been set by the server once it authenticates the user. Server goes ahead and sends back this authentication cookie along with the response back to the client. Now next time the client sends back any request in the same session it should have the authentication cookie as well apart from the Session Cookie that was set earlier. Server will recognize the user and the ongoing session with the client based on the cookies sent to it in future requests.
Step 7: Now is the final round wherein Client after done with all the validation process etc, goes ahead and sends request for the Default.aspx page (remember it was also the very first request sent by the client in the whole process).
From client
GET /FormsAuthentication/default.aspx HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Referer: https://saurabsi-sec/FormsAuthentication/login.aspx?ReturnUrl=%2fFormsAuthentication%2fdefault.aspx
Accept-Language: en-us
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 1.1.4322; InfoPath.2)
Proxy-Connection: Keep-Alive
Host: saurabsi-sec
Pragma: no-cache
Cookie: AppSessionCookie=vtd2qg55mkcypqnn53obxm45; FormsAuthCookie=82A747623272A0A3A0C36EC6AD1FBA35A47592B1CFB38E54A1A7C5BC24ECAA2563715D4225EAE8927B98EC3DAD6FB9875E67FCA344AECCA19837A40B2311E373
Notice the cookies here. You will see there are two cookies being sent to the server separated by ";". One for the server to recognize the ongoing session with the client and the other one to recongize the authenticated user.
Step 8: Server has nothing else to do much but send back the response to the client for the requested web page.
From Server
HTTP/1.1 200 OK
Date: Thu, 19 Apr 2007 07:11:50 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
MicrosoftOfficeWebServer: 5.0_Pub
X-AspNet-Version: 2.0.50727
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1111
You might notice something here, in the web.config file we had set the timeout value for Forms authentication cookie to be 1 minute, and session cookie to be 2 minutes. So, you might see a scenario wherein let's say a user gets logged out because of expired authentication cookie. In our example let's say a user gets logged out after 1 minute (authentication cookie timeout value being set to 1 minute), and he is redirected to login page. He logs in back this time after entering the credentials in the login page. But this time he uses a different credentials to login.
So what should happen, should the new user (with a different credentials this time) have access to all the original session variables (assuming session timeout has still not expired for the earlier user session) of the previous user when the same browser instance is running?
Answer is Yes, the new user with a different credentials this time will have access to all the session variables for the previous user, provided the same browser session is being used this time. Resaon being that the authentication cookie has expired but not the session cookie, so browser sends the vaild session cookies to the server and hence is able to access the session variables.
If we use a different browser session, of course a new session cookie has to be obtained which will invalidate session variables for the prevoius user.
Here I haven't gone into troubleshooting session loss issues, rather i have focused on how forms authentication process occurs between client and the server. In case your eyes are looking for some good logical reading on troubleshooting Session loss issues in ASP.Net, a must READ here https://aspalliance.com/1182_Troubleshooting_Session_Related_Issues_in_ASPNET
Comments
Anonymous
April 20, 2007
PingBack from http://mhinze.com/links-for-2007-04-20/Anonymous
May 08, 2007
Hello! Great site! I've found a lot information here. I don't know how to thank you. I hope you'll be writing more and more. Thank you again. Bye.Anonymous
May 09, 2007
Hello! Very interesting. Thank you.Anonymous
August 29, 2007
Great information about the http traffic.. Thanks alot.. KhurramAnonymous
November 15, 2007
Saurabh, Great site! Good work I also appreciate your dedication when you worked with a member of team. Keep it up. Thanks, Manoj ParameswaranAnonymous
January 16, 2008
Very detailed information. I do not think I can keep all this control flow readilly accessible in memory but it is fascinating to know what is going on. Currently I am working on implementing forms authentication and going to use the knowledge that I gained from these articles. bye Ramesh www.asnowfall.com