IT:AD:ASP.NET:Common:HowTo:HttpModule:FormsAuthenticationModule
Summary
Notes
Activation
Web Authentication is by default one of:
WindowsAuthentication(using IT:AD:ASP.NET:Common:HowTo:WindowsAuthenticationModule)FormsAuthentication*Passport(defunct)None(which is a bit of a misnomer, as a more appropriate name would have beenNoneOrCustom).
You activate FormsAuthentication in the web.config file.
Setting Authentication Mode in Web.Config
The choice of which authentication method is used is determined in web.config.
<configuration>
<system.web>
...
<authentication mode="Forms">
<!-- Notes:
Url is relative, to a page, or handler (if bouncing out to an SSO)
cookieless should always be set to UseCookies
Timeout value is in minutes -->
<forms cookieless="UseCookies"
loginUrl="/demo/login.ashx"
name="DemoServiceProvider"
timeout="20"/>
</authentication>
...
</configuration>
Sequence Diagrams
FormsAuthentication works via a combination of Redirects, Cookies, Identity, Principal, and AnonymousIdentity.
The following sequence diagram will make part of what's going on clearer.
All Requests
As per IT:AD:ASP.NET:Common:HowTo:HttpModule, whereas HttpHandlers handle a specific registered route, all requests go through all HttpModules.
Order of Processing
It's important to notice that as per Order of Execution, FormsAuthenticationModule processes requests before UrlAuthorisationModule and FileAuthorisationModule.
Events Handled
The Module handles the following two events only if the AuthenticationMode == Forms, and not Windows, etc
(I sort of feel that it should allow for both).
public void Init(HttpApplication app)
{
//pseudocode:
//exit now if AuthenticationConfig.Mode != AuthenticationMode.Forms;
//otherwise, wire up the event handlers
app.AuthenticateRequest += new EventHandler(this.OnEnter);
app.EndRequest += new EventHandler(this.OnLeave);
}
Concentrating on the AuthenticationRequest event handler first, this is what happens:
private void OnEnter(object source, EventArgs eventArgs)
{
HttpContext context = ((HttpApplication) source).Context;
//VERY IMPORTANT:
//If *any* earlier Module has already created a User at this point,
//do NOT change, and get out early:
if (e.Context.User != null) {return;}
...
// Pseudo code steps:
// =============
// Get `FormsAuthenticationTicket` from Cookie (using FormsAuthentication.FormsCookieName):
// If null/Expired exit;
// Renew ticket (adjust sliding expiration)
// Create new FormsIdentity from ticket
// Create GenericPrincipal from FormsIdentity
// set e.Context.User
// create new ticket, encrypt, remove existing, reattach
//Not sure about this part just yet and no more time tonight:
if (AuthenticationConfig.AccessingLoginPage(context, FormsAuthentication.LoginUrl))
{
context.SetSkipAuthorizationNoDemand(true, false);
cookielessHelper.RedirectWithDetectionIfRequired((string) null, FormsAuthentication.CookieMode);
}
if (context.SkipAuthorization)
return;
context.SetSkipAuthorizationNoDemand(AssemblyResourceLoader.IsValidWebResourceRequest(context), false);
}
OnLeave
private void OnLeave(object source, EventArgs eventArgs)
{
//Get HttpContext
//If we are not handling a 401 raised by UrlAuthorisationModule or else
//no need to do anything else (the Response ticket is already set)
if (context.Response.StatusCode != 401 || context.Response.SuppressFormsAuthenticationRedirect)
return;
//If looping back, don't redo this.
if (!this._fOnEnterCalled)
return;
this._fOnEnterCalled = false;
string rawUrl = context.Request.RawUrl;
if (rawUrl.IndexOf("?" + FormsAuthentication.ReturnUrlVar + "=", StringComparison.Ordinal) != -1 || rawUrl.IndexOf("&" + FormsAuthentication.ReturnUrlVar + "=", StringComparison.Ordinal) != -1)
return;
string strUrl = (string) null;
if (!string.IsNullOrEmpty(FormsAuthentication.LoginUrl))
strUrl = AuthenticationConfig.GetCompleteLoginUrl(context, FormsAuthentication.LoginUrl);
if (strUrl == null || strUrl.Length <= 0)
throw new HttpException(SR.GetString("Auth_Invalid_Login_Url"));
CookielessHelperClass cookielessHelper = context.CookielessHelper;
string str;
if (strUrl.IndexOf('?') >= 0)
str = FormsAuthentication.RemoveQueryStringVariableFromUrl(strUrl, FormsAuthentication.ReturnUrlVar) + "&" + FormsAuthentication.ReturnUrlVar + "=" + HttpUtility.UrlEncode(rawUrl, context.Request.ContentEncoding);
else
str = strUrl + "?" + FormsAuthentication.ReturnUrlVar + "=" + HttpUtility.UrlEncode(rawUrl, context.Request.ContentEncoding);
int num = rawUrl.IndexOf('?');
if (num >= 0 && num < rawUrl.Length - 1)
str = str + "&" + rawUrl.Substring(num + 1);
cookielessHelper.SetCookieValue('F', (string) null);
cookielessHelper.RedirectWithDetectionIfRequired(str, FormsAuthentication.CookieMode);
context.Response.Redirect(str, false);
}