# IT:AD:ASP.NET:Common:HowTo:HttpModule:FormsAuthenticationModule # * [[../|(UP)]] {{indexmenu>.#2|nsort tsort}} ## 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 been `NoneOrCustom`*). 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`. ... ... ### 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. Participant UserAgent Participant FormsAuthenticationModule Participant UrlAuthorisationModule UserAgent -> FormsAuthenticationModule : Request Protected.aspx FormsAuthenticationModule -> UrlAuthorisationModule : Anonymous Request UrlAuthorisationModule --> FormsAuthenticationModule : 401 (Unauthorized) FormsAuthenticationModule --> UserAgent : 302 (redirect to Login.aspx) UserAgent -> FormsAuthenticationModule : Request Login.aspx FormsAuthenticationModule -> UrlAuthorisationModule : Anonymous Request UrlAuthorisationModule -> LoginPage : Authorized LoginPage -> UserAgent : Render Resource UserAgent -> FormsAuthenticationModule : Postback Credentials FormsAuthenticationModule -> UrlAuthorisationModule : Anonymous Request UrlAuthorisationModule -> LoginPage : Authorized LoginPage --> UserAgent : 302 (redirect to Protected.aspx, adding Auth Ticket to Cookie) UserAgent -> FormsAuthenticationModule : Request Protected.aspx FormsAuthenticationModule -> FormsAuthenticationModule : Attach Identity to Thread FormsAuthenticationModule -> UrlAuthorisationModule : Authenticated UrlAuthorisationModule -> Protected.aspx : Authorized Protected.aspx -> UserAgent : Render Resource ### All Requests As per [[IT/AD/ASP.NET/Common/HowTo/HttpModule/]], whereas `HttpHandler`s handle a specific registered route, all requests go through all `HttpModule`s. ### Order of Processing It's important to notice that as per [[IT/AD/ASP.NET/Common/HowTo/HttpModule/HowTo/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); }