Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forms authentication: disable redirect to the login page

I have an application that uses ASP.NET Forms Authentication. For the most part, it's working great, but I'm trying to add support for a simple API via an .ashx file. I want the ashx file to have optional authentication (i.e. if you don't supply an Authentication header, then it just works anonymously). But, depending on what you do, I want to require authentication under certain conditions.

I thought it would be a simple matter of responding with status code 401 if the required authentication was not supplied, but it seems like the Forms Authentcation module is intercepting that and responding with a redirect to the login page instead. What I mean is, if my ProcessRequest method looks like this:

public void ProcessRequest(HttpContext context) {     Response.StatusCode = 401;     Response.StatusDescription = "Authentication required"; } 

Then instead of getting a 401 error code on the client, like I expect, I'm actually getting a 302 redirect to the login page.

For nornal HTTP traffic, I can see how that would be useful, but for my API page, I want the 401 to go through unmodified so that the client-side caller can respond to it programmatically instead.

Is there any way to do that?

like image 895
Dean Harding Avatar asked May 15 '10 08:05

Dean Harding


2 Answers

ASP.NET 4.5 added the Boolean HttpResponse.SuppressFormsAuthenticationRedirect property.

public void ProcessRequest(HttpContext context) {     Response.StatusCode = 401;     Response.StatusDescription = "Authentication required";     Response.SuppressFormsAuthenticationRedirect = true; } 
like image 51
zacharydl Avatar answered Oct 05 '22 04:10

zacharydl


After a bit of investigation, it looks like the FormsAuthenticationModule adds a handler for the HttpApplicationContext.EndRequest event. In it's handler, it checks for a 401 status code and basically does a Response.Redirect(loginUrl) instead. As far as I can tell, there's no way to override this behaviour if want to use FormsAuthenticationModule.

The way I ended up getting around it was by disabling the FormsAuthenticationModule in the web.config like so:

<authentication mode="None" /> 

And then implementing the Application_AuthenticateEvent myself:

void Application_AuthenticateRequest(object sender, EventArgs e) {     if (Context.User == null)     {         var oldTicket = ExtractTicketFromCookie(Context, FormsAuthentication.FormsCookieName);         if (oldTicket != null && !oldTicket.Expired)         {             var ticket = oldTicket;             if (FormsAuthentication.SlidingExpiration)             {                 ticket = FormsAuthentication.RenewTicketIfOld(oldTicket);                 if (ticket == null)                     return;             }              Context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]);             if (ticket != oldTicket)             {                 // update the cookie since we've refreshed the ticket                 string cookieValue = FormsAuthentication.Encrypt(ticket);                 var cookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName] ??                              new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath };                  if (ticket.IsPersistent)                     cookie.Expires = ticket.Expiration;                 cookie.Value = cookieValue;                 cookie.Secure = FormsAuthentication.RequireSSL;                 cookie.HttpOnly = true;                 if (FormsAuthentication.CookieDomain != null)                     cookie.Domain = FormsAuthentication.CookieDomain;                 Context.Response.Cookies.Remove(cookie.Name);                 Context.Response.Cookies.Add(cookie);             }         }     } }  private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name) {     FormsAuthenticationTicket ticket = null;     string encryptedTicket = null;      var cookie = context.Request.Cookies[name];     if (cookie != null)     {         encryptedTicket = cookie.Value;     }      if (!string.IsNullOrEmpty(encryptedTicket))     {         try         {             ticket = FormsAuthentication.Decrypt(encryptedTicket);         }         catch         {             context.Request.Cookies.Remove(name);         }          if (ticket != null && !ticket.Expired)         {             return ticket;         }          // if the ticket is expired then remove it         context.Request.Cookies.Remove(name);         return null;     } } 

It's actually slightly more complicated than that, but I basically got the code by looking at the implementation of FormsAuthenticationModule in reflector. My implementation is different to the built-in FormsAuthenticationModule in that it doesn't do anything if you respond with a 401 - no redirecting to the login page at all. I guess if that ever becomes a requirement, I can put an item in the context to disable the auto-redirect or something.

like image 44
Dean Harding Avatar answered Oct 05 '22 05:10

Dean Harding