Chitika

March 14, 2012

ASP.NET MVC 4 WebAPI authorization

In the examples of ASP.NET MVC 4 WebAPI you can find that authorization is really easy.
You just have to add [Authorize] attribute to your controller or for some actions which need it.

And if you do this you will expect that your WebAPI will return error code 401 (Not authorized) to your client.

But unfortunately it's not happened.
And you can ask: what will happen in the real world?
I can answer: your browser just get the 302 (Found) status code and will redirect you to the login page.

I think it's not expected behavior for you. Because you want to get just 401 status code in the client.

And I can show you how you can achieve it.

1. Please, double sure that you use AuthorizeAttribute from the System.Web.Http library instead of one from the System.Web.Mvc.

2. Add a directory and two classes to your code which should "FIX" it:

- Add App_Start directory to your solution.
       We will create two classes in this directory in a minute.

Add first class into that folder:  
       This class is a HTTP module which do all the 'magic'.
       The 'magic' is simple - just set the marker in PosrReleaseRequestState event if this is an ajax request and if our action returns status code equals to 401 (Unauthorized) or 403 (Forbidden). Then in OnEndRequest we have to check is this marker exists, and if yes, then set the returned status code from an action back.
public class AjaxFormsAuthenticationModule : IHttpModule

    {

        private const string FixupKey = "__WEBAPI:Authentication-Fixup";

        public void Dispose()

        {

        }

        public void Init(HttpApplication context)

        {

            context.PostReleaseRequestState += OnPostReleaseRequestState;

            context.EndRequest += OnEndRequest;

        }

        private void OnPostReleaseRequestState(object source, EventArgs args)

        {

            var context = (HttpApplication)source;

            var response = context.Response;

            var request = context.Request;

            bool isAjax = request.Headers["X-Requested-With"] == "XMLHttpRequest";

            if ((response.StatusCode == 401 || response.StatusCode == 403) && isAjax)

            {

                context.Context.Items[FixupKey] = response.StatusCode;

            }

        }

        private void OnEndRequest(object source, EventArgs args)

        {

            var context = (HttpApplication)source;

            var response = context.Response;

            if (context.Context.Items.Contains("__WEBAPI:Authentication-Fixup"))

            {

                response.StatusCode = (int)context.Context.Items[FixupKey];

                response.RedirectLocation = null;

            }

        }

    }

Add second class into that folder:
       This class will just register our HTTP module for our application..
using Microsoft.Web.Infrastructure.DynamicModuleHelper;

[assembly: PreApplicationStartMethod(typeof(FormsAuthenticationFixer), "Start")]

namespace LoginVS11.App_Start

{

    public static class FormsAuthenticationFixer

    {

        public static void Start()

        {

            DynamicModuleUtility.RegisterModule(typeof(AjaxFormsAuthenticationModule));

        }

    }

}

That's all.

After that all of your ajax requests will get the 401 status code if your client is not authorized yet.

6 comments:

  1. Thanks, this really helped explain it.

    How would you go about handling authentication of users in a WebAPI?

    ReplyDelete
    Replies
    1. It's not easy question, because if you would like to strict go with RESTful kind of WebAPI then you have to create some token/keys mechanism between client and server.

      But if you are ready to move a little bit from strict RESTful way then of course there are a lot of simplest ways to do authentication.

      I will describe one of them in my next topic.

      Delete
  2. Any chance you have a guide about how to autherize using restapi in mvc4? I want to login from a WP7 application without the user going to a website so I guess I have to return a AuthorizeAttribute somehow in the Header?

    ReplyDelete
  3. Couldn't you just use System.Web.Http.AuthorizeAttribute instead of System.Web.Mvc.AuthorizeAttribute? If I'm not mistaken, it returns an error response with a 401 status code.

    Sam

    ReplyDelete
  4. I can think of many scenarios when building device agnostic mobile applications that need to communicate with a service where a token based or ApplicationID / secret key type of solution would be useful. Any thoughts on best practices would be really helpful.

    ReplyDelete
  5. Dude, this looked pretty interesting until I saw that you defined a constant and then didn't bother to use it on line 53.

    ReplyDelete