A J Qarshi
A J Qarshi

Reputation: 2992

Check MVC Controller Permission and return 401 Unauthorized to angularJS app

I have an angularJS application which communicates with MVC Controller to get some data.

I have implemented a class RBACAuthorizeAttribute which inherits from AuthorizeAttribute to check if the user has permission to execute the particular action on the controller. Implementation of this class is shown below:

public class RBACAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
        var webAction = filterContext.ActionDescriptor.ActionName;
        var user = filterContext.HttpContext.User.Identity.Name;


        if (!HasPermission(controllerName, webAction, user))
        {
            filterContext.Result = new RedirectToRouteResult(
                                           new RouteValueDictionary {
                                            { "action", "Index" },
                                            { "controller", "Unauthorized" } });
        }            
    }

    private bool HasPermission(string controllerName, string webAction, string user)
    {
        //Check if the user has permission here.
        //return true or false.
        return false; 
    }
}

The actual controller on which I am checking the permission is given below:

public class MyDataController : Controller
{
    [Route("IndividualDetails/{id}")]
    [RBACAuthorize]
    public JsonResult GetIndividualDetails(string id)
    {
        var data = GetDataFromSomeTableInDatabase(id);

        return data;
    }

UnauthorizedController class definition is:

public class UnauthorizedController : Controller
{
    public ActionResult Index()
    {
        return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
    }
} 

Below is the AngularJS function which invokes the controller:

function getDetails(id) {
        return $resource(remoteServiceUrl + '/MyData/IndividualDetails/:id', { id: id })
            .get().$promise;
    };  

function loadUserDetails(id) {
    getDetails(id).then(
        function (result) {
            console.log(result);
        }, 
        function (reason) {
            console.log(reason);
        }
    );
}

I am expecting the 401-Unauthorized response in the above loadUserDetails function but I am getting quite a big array in result which contains some html page. The issue seems to be in the implementation of RBACAuthorizeAttribute class. So to get the 401-Unauthorized error on client side, can anyone point me to the right direction please.

Edit: I am using Asp.Net Identity system and the version of Asp.NET is 5.

Upvotes: 1

Views: 2704

Answers (2)

Craig W.
Craig W.

Reputation: 18175

I've run into this very thing. When you return the 401 the ASP.NET infrastructure is redirecting you to the login page. If you examine the bytes that come back from your call I would be willing to bet it's either your login page or an error page (if you don't have a login page defined).

To solve this you need to modify the Provider for your CookieAuthenticationOptions and implement your own 'OnApplyRedirect' method to only do the redirect on a 401 if it's not an AJAX request.

Somewhere in your startup code you'll have something like:

var options = new CookieAuthenticationOptions
{
    LoginPath = new PathString(loginPath),
    CookieSecure = CookieSecureOption.Always,
    CookieName = cookieName,

    Provider = new CookieAuthenticationProvider()
    {
        OnApplyRedirect = context =>
        {
            if (!context.Request.IsAjaxRequest())
            { context.Response.Redirect(context.RedirectUri); }
        }
    }
};

app.UseCookieAuthentication(options);

The variables loginPath and cookieName should contain the path to your login page/action and the name of the cookie your site uses respectively (e.g. "/Auth" and ".MySiteAuthCookie").

I originally the answer in the following blog post and tweaked it a bit for our environment.

http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes

Upvotes: 0

Darin Dimitrov
Darin Dimitrov

Reputation: 1039418

I am expecting the 401-Unauthorized response in the above loadUserDetails function but I am getting quite a big array in result which contains some html page

I suppose that this HTML page is the login page. You could add a custom key to the HttpContext:

public class UnauthorizedController : Controller
{
    public ActionResult Index()
    {
        this.HttpContext.Items["SuppressAuthenticationKey"] = true;
        return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
    }
}

and then in your Global.asax subscribe to the EndRequest method and prevent the redirect if this key is present:

protected void Application_EndRequest(object sender, EventArgs e)
{
    var context = (HttpApplication)sender;
    var response = context.Response;

    if (context.Context.Items.Contains("SuppressAuthenticationKey"))
    {
        response.TrySkipIisCustomErrors = true;
        response.ClearContent();
        response.StatusCode = 401;
        response.RedirectLocation = null;
    }
}

If you are using Forms Authentication and .NET 4.5 or later you could set the SuppressFormsAuthenticationRedirect property to true in your Unauthorized action.

Also I see it kind of redundant to make an additional redirect to your Unauthorized action. You could directly return 401 from your custom RBACAuthorizeAttribute:

filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Unauthorized);

Upvotes: 2

Related Questions