Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In-place handling (no redirect) of 401 unauthorized?

QUESTION

What is a simple way to return a 401 and sign-in page directly, avoiding a 302 redirect, when an IAuthorizationFilter fails in my ASP.NET MVC 5 application on IIS 7.0, and what infrastructure must I implement to make this work?

Possible alternate question: Is this a bad idea?

-

BACKGROUND

I implemented the Application_EndRequest() shim to address AJAX issues caused by FormsAuthentication. It converts all 401 UNAUTHORIZED TO 302 FOUND (i.e. redirect). However, that caused additional problems, because now all 302 redirects are converted to 401's. So to fix the fix, I got rid of redirects for 500 SERVER ERROR, and for 404 NOT FOUND, and I think there are a still couple I missed...

Maybe I'm over-engineering this, but after this exercise it seems incorrect to return a 302 for a 401 in the first place. Solved one problem, created new ones. I think the technically ideal answer is to handle 401's in-place. Then the server is sending an honest status code whether AJAX or not, and no big deal if the AJAX client receives sign-in HTML.

-

MY PROGRESS

Using FormsAuthentication cookies without FormsAuthentication

I tried one approach, by using:

<httpErrors errorMode="Custom" existingResponse="Replace">
    <error statusCode="401" responseMode="ExecuteURL" path="/Account/AuthReq" />
</httpErrors>

However, to make that work I have to pull out FormsAuthentication from Web.config, and when I pull out forms authentication, my custom AuthorizeAttribute stops functioning. I think there's very little FormsAuthentication code left that I'm actually using, but I'm a little fearful of getting rid of it because I don't want to apply my own ignorance to managing a secure authentication cookie.

To the best of my knowledge, the code below represents the only place I currently depend on FormsAuthentication, if I don't desire the 401 -> 302 handling.

/// <summary>
///     Stores this SessionIdentity in the Forms Authentication token for use next request
/// </summary>
public void Write(HttpContext httpContext) {
    httpContext.Response.AppendCookie(FormsAuthentication.GetAuthCookie(serialize(), false));
}

/// <summary>
///     Constructs a SessionIdentity from the Forms Authentication token
/// </summary>
public static SessionIdentity Read(HttpContext httpContext) {
    IIdentity identity = httpContext.User.Identity;
    return identity.IsAuthenticated
                ? deserialize(identity.Name)
                : Unauthenticated;
}

/// <summary>
///     Removes the SessionIdentity Forms Authentication token for the next request
/// </summary>
public static void Clear(HttpContext httpContext) {
    var clearAuthCookie = new HttpCookie(FormsAuthentication.FormsCookieName, "");
    clearAuthCookie.Expires = DateTime.Now.AddYears(-1);
    httpContext.Response.Cookies.Add(clearAuthCookie);
}

It seems I may be able to avoid the dependency on FormsAuthentication in the Web.Config and still be able to use OEM encrypted tickets, just by leveraging FormsAuthentication.Decrypt() rather than HttpContext.User.Identity where I do above. I haven't investigated yet whether this change would be related to the fairly new Claims-based tickets or the very new AuthenticationFilter in MVC5.

Using AppHarbor's open authentication library for .NET

Although I'm not certain it's true for my scenario, a few answers to similar topics here have stated it isn't possible to disable the 302 redirect action of FormsAuthentication. It does however seem clear that there is no alternate authentication mechanism included with ASP.NET.

AppHarbor has published a replacement library to GitHub, and discussed it at their blog.

like image 711
shannon Avatar asked Dec 11 '22 09:12

shannon


1 Answers

Check the SuppressFormsAuthenticationRedirect property out.

And then write a custom Authorize attribute:

public class MyAuthorizeAttribute: AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        filterContext.HttpContext.Response.StatusCode = 401;
        filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
        filterContext.Result = new ViewResult
        {
            ViewName = "~/Views/Account/Login.cshtml",
        };
    }
}

which could be used to decorate your protected controllers/actions with:

[MyAuthorize]
public ActionResult Admin()
{
    return View();
}

Obviously you might also want to get rid of any custom error pages in your web.config for 401 status codes if you don't want them to take over.

like image 179
Darin Dimitrov Avatar answered Jan 05 '23 17:01

Darin Dimitrov