16

This is my Global.asax.cs file:

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        ...
    }

    protected void Application_Start()
    {
        this.PostAuthenticateRequest += new EventHandler(MvcApplication_PostAuthenticateRequest);
    }

    // This method never called by requests...
    protected void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
    {
        HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];

        if (authCookie != null)
        {
            FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
            var identity = new GenericIdentity(authTicket.Name, "Forms");
            var principal = new GenericPrincipal(identity, new string[] { });
            Context.User = principal;
        }
    }
}

When PostAuthenticateRequest gets execute?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Jalal
  • 6,594
  • 9
  • 63
  • 100

2 Answers2

24

According to the documentation:

Occurs when a security module has established the identity of the user.

...

The PostAuthenticateRequest event is raised after the AuthenticateRequest event has occurred. Functionality that subscribes to the PostAuthenticateRequest event can access any data that is processed by the PostAuthenticateRequest.

And here's the ASP.NET Page Life Cycle.

But because your question is tagged with ASP.NET MVC I would strongly recommend you performing this into a custom [Authorize] attribute instead of using this event. Example:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var isAuthorized = base.AuthorizeCore(httpContext);
        if (isAuthorized)
        {
            var authCookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
            if (authCookie != null)
            {
                var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
                var identity = new GenericIdentity(authTicket.Name, "Forms");
                var principal = new GenericPrincipal(identity, new string[] { });
                httpContext.User = principal;
            }
        }
        return isAuthorized;
    }
}

Now decorate your controllers/actions with the [MyAuthorize] attribute:

[MyAuthorize]
public ActionResult Foo()
{
    // if you got here the User property will be the custom
    // principal you injected in the authorize attribute
    ...
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 5
    Why are you recommending applying filters on all controllers, when it seems much cleaner to have the change in a single place on the event handler? What is the gain? – Andrew Savinykh Nov 21 '11 at 23:49
  • 1
    For this example, it would probably make more sense to replace var identity = new GenericIdentity(authTicket.Name, "Forms"); with var identity = new FormsIdentity(authTicket); – Scott Coates Mar 28 '12 at 02:39
  • 6
    @zespri The PostAuthenticateRequest event may be called many times per page. Using a custom authorize attribute ensures your code will only be called once per request. – Mark Mar 29 '12 at 00:41
  • I tried PostAuthenticate and Windows Authentication approach combined and got the issue http://stackoverflow.com/questions/14439497/mixing-windows-authentication-with-sql-server-custom-authentication-mvc3 . Now i am planning to take your advise and implement. I hope it will not make any issue – Murali Murugesan Jan 21 '13 at 13:19
  • @Darin Dimitrov Should I also set Thread.CurrentPrincipal as you did for HttpContext.User? (i.e. System.Threading.Thread.CurrentPrincipal = principal;) – SherleyDev Apr 22 '14 at 14:40
10

If you place your code on PostAuthenticateRequest you may get hit many times per request as every resource such as images and style sheets referenced on your page will trigger this event as they are treated as separate requests.

If you go with @Darin's answer, the AuthorizeAttribute won't render the action when isAuthorized returns false, but people may need it to be rendered anyway, even if its a public page (unrestricted access) you may want to show a "Display Name" saved on the userData part of the authTicket.

For that, I recommend loading the authCookie on an ActionFilterAttribute (AuthenticationFilter):

public class LoadCustomAuthTicket : ActionFilterAttribute, IAuthenticationFilter
{
    public void OnAuthentication(AuthenticationContext filterContext)
    {
        if (!filterContext.Principal.Identity.IsAuthenticated)
            return;

        HttpCookie authCookie = filterContext.HttpContext.Request.Cookies[FormsAuthentication.FormsCookieName];

        if (authCookie == null)
            return;

        FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        var identity = new GenericIdentity(authTicket.Name, "Forms");
        var principal = new GenericPrincipal(identity, new string[] { });

        // Make sure the Principal's are in sync. see: https://www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthenticationCanBeSubtle.aspx
        filterContext.Principal = filterContext.HttpContext.User = System.Threading.Thread.CurrentPrincipal = principal;

    }
    public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
    {
        //This method is responsible for validating the current principal and permitting the execution of the current action/request.
        //Here you should validate if the current principle is valid / permitted to invoke the current action. (However I would place this logic to an authorization filter)
        //filterContext.Result = new RedirectToRouteResult("CustomErrorPage",null);
    }
}

And on global.asax.cs

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new LoadCustomAuthTicket());
    }

That way you also won't have to populate all your actions with the attribute.

Joao Leme
  • 9,598
  • 3
  • 33
  • 47
  • Would this action filter step on the toes of a custom authorize attribute if both are applied to an action? – Andrew Hoffman Mar 18 '15 at 14:02
  • Also, would there be a way to ensure that this attribute executed before an authorize attribute? Ideally we'd only load the cookie at one spot, before any attribute executes. – Andrew Hoffman Mar 18 '15 at 14:04
  • Found some relevant and interesting order of operation info. http://stackoverflow.com/questions/6561883/in-what-order-are-filters-executed-in-asp-net-mvc – Andrew Hoffman Mar 18 '15 at 14:46
  • I just updated changing to AuthenticationFilter to get his first, since after upgrading to MVC5 .net 4.5 the previous code on ActionFilter was not working. – Joao Leme Mar 25 '17 at 19:04