Supertext Home
Chief of the System Blog

Basic Http Authorization for Web API in MVC 4 Beta

April 19th, 2012 by

A little while ago I posted a solution to do Basic Http Authorization with the Web API Preview 6. Web API got then merged into the next ASP.NET MVC 4 Beta Release and in the process has changed a lot.

Since my old approach did not work anymore, I had to create something new.

 

Usage:

public class OrderController : ApiController
{
    // GET /api/orders/5
    [BasicHttpAuthorizeAttribute(RequireAuthentication = true)]
    public string Get(int id, string communicationLang)
    {
        //do your API Stuff
    }
}

And the Authentication class itself:

public class BasicHttpAuthorizeAttribute : System.Web.Http.AuthorizeAttribute  
{
    bool requireSsl = Convert.ToBoolean(ConfigurationManager.AppSettings["RequireSsl"]);
 
    public bool RequireSsl
    {
        get { return requireSsl; }
        set { requireSsl = value; }
    }
 
 
    bool requireAuthentication = true;
 
    public bool RequireAuthentication
    {
        get { return requireAuthentication; }
        set { requireAuthentication = value; }
    }
 
 
    /// <summary>
    /// For logging with Log4net.
    /// </summary>
    private static readonly ILog log = LogManager.GetLogger(typeof(BasicHttpAuthorizeAttribute));
 
 
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)        
    {
        //actionContext.Request
 
        if (Authenticate(actionContext) || !RequireAuthentication)
        {
            return;
        }
        else
        {
            HandleUnauthorizedRequest(actionContext);
        }
    }
 
    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
        throw new HttpResponseException(challengeMessage);
        //throw new HttpResponseException();
    }
 
 
    private bool Authenticate(System.Web.Http.Controllers.HttpActionContext actionContext) //HttpRequestMessage input)
    {
        if (RequireSsl && !HttpContext.Current.Request.IsSecureConnection && !HttpContext.Current.Request.IsLocal)
        {
            log.Error("Failed to login: SSL:" + HttpContext.Current.Request.IsSecureConnection);
            return false;
        }
 
        if (!HttpContext.Current.Request.Headers.AllKeys.Contains("Authorization")) return false;
 
        string authHeader = HttpContext.Current.Request.Headers["Authorization"];
 
        IPrincipal principal;
        if (TryGetPrincipal(authHeader, out principal))
        {
            HttpContext.Current.User = principal;
            return true;
        }
        return false;
    }
 
 
    private bool TryGetPrincipal(string authHeader, out IPrincipal principal)
    {
        var creds = ParseAuthHeader(authHeader);
        if (creds != null)
        {
            if (TryGetPrincipal(creds[0], creds[1], out principal)) return true;
        }
 
        principal = null;
        return false;
    }
 
 
    private string[] ParseAuthHeader(string authHeader)
    {
        // Check this is a Basic Auth header 
        if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;
 
        // Pull out the Credentials with are seperated by ':' and Base64 encoded 
        string base64Credentials = authHeader.Substring(6);
        string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });
 
        if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[0])) return null;
 
        // Okay this is the credentials 
        return credentials;
    }
 
 
    private bool TryGetPrincipal(string username, string password, out IPrincipal principal)
    {
        // this is the method that does the authentication 
 
        //users often add a copy/paste space at the end of the username
        username = username.Trim();
        password = password.Trim();
 
        //TODO
        //Replace this with your own Authentication Code
        Person person = AccountManagement.ApiLogin(username, password);
 
        if (person != null)
        {
            // once the user is verified, assign it to an IPrincipal with the identity name and applicable roles
            principal = new GenericPrincipal(new GenericIdentity(username), System.Web.Security.Roles.GetRolesForUser(username));
            return true;
        }
        else
        {
            if (!String.IsNullOrWhiteSpace(username))
            {
                log.Error("Failed to login: username=" + username + "; password=" + password);
            }
            principal = null;
            return false;
        }
    }
}

You will have to adjust the TryGetPrincipal() method to include our own Authorization code that works with your system. Or maybe plain normal ASP.NET Provider Authentication is enough in your case.

You can download the code from github:
https://github.com/rblaettler/BasicHttpAuthorization

20 Comments to “Basic Http Authorization for Web API in MVC 4 Beta”

  • Ivan says on June 9th, 2012 at 11:31 am :

    Puting authentication in my rest service it has been a nightmare. Thanks a lot for your post.

  • Chris says on June 24th, 2012 at 4:25 pm :

    This code works fine under the built in web server in Visual Studio 2010 but it does NOT work under IIS 7.5 by default.

    To get it to work under IIS 7.5, you need to turn off all authentication mechanisms for your website within IIS EXCEPT “Anonymous Authentication”. See http://technet.microsoft.com/en-us/library/cc733010%28v=ws.10%29.aspx

    This class is basically rolling its own basic http authentication and having IIS’s auth mechanisms enabled seems to cause a conflict.

  • Rémy Blättler says on June 25th, 2012 at 3:19 am :

    Thanks for your hint. I forgot to mention that.

  • george says on July 3rd, 2012 at 5:37 am :

    Two things.

    1. I’m never getting a value of True for RequireAuthentication . It’s always false.

    2. On one of my controllers the OnAuthorization method gets called twice.

    why?

  • Rémy Blättler says on July 3rd, 2012 at 5:41 am :

    You’ve set it up like this:
    [BasicHttpAuthorizeAttribute(RequireAuthentication = true)]

    And this does not come through? Cause this is just basic framework functionality.

  • george says on July 9th, 2012 at 2:43 am :

    I had an unrelated problem. Works great.

  • Sam says on July 19th, 2012 at 12:15 pm :

    Thanks for the post this has been very helpful.

    I am having one issue. I set HttpContext.Current.User = principal in the Authenticate however in my controller this.User.Identity in not set to the principalI set – it is basically blank (IsAuthnticated = False and Name = “”). This is a problem as I need this information in my controller.

    Is something overwriting it?

  • Rémy Blättler says on July 23rd, 2012 at 9:38 pm :

    Not that I know of. Did you try Membership.GetUser()?

  • Sam says on July 24th, 2012 at 10:56 am :

    Unfortunately I am not using the membership provider. I am using a custom authentication solution and I would like the assign the username of the authenticated user to HttpContext.Current.User.

  • Rémy Blättler says on July 24th, 2012 at 2:05 pm :

    Right. That should be possible where I do this:

    TryGetPrincipal()

    Just replace my authentication code with your solution.

  • Basic Authentication with WCF Web API Preview 6 | Chief of the System Blog says on July 30th, 2012 at 5:56 pm :

    [...] Basic Http Authorization for Web API in MVC 4 Beta [...]

  • Lelala says on April 4th, 2013 at 11:03 am :

    Wow, better example than the one supplied by Microsoft themselves. Thanks for shortening their version :-)

  • Ajosh says on May 3rd, 2013 at 2:36 pm :

    Thanks a lot. This helped me a lot.

  • xiaobao says on May 21st, 2013 at 6:16 pm :

    Hi

    I get an error on GenericPrinciple line

    The Role Manager feature has not been enabled.

    I am using a custom database(not asp.net membership). Can I use that part what you wrote? If so how do I fix this error?

  • Rémy Blättler says on May 22nd, 2013 at 3:42 am :

    Do you use a totally different authentication method? E.g. you never us the Membership and Role classes?
    Then you just have to change a few more places and get rid of the IPrincipal stuff completely and replace it with your own authentication code.

  • xiaobao says on May 22nd, 2013 at 12:48 pm :

    No.

    As right now I have my own role and user table and not using any of the built in stuff as I guess I would have to write my own membership stuff then.

    Ok then I will just replace it with my own code. I am not sure if this is what you where going for with “RequireAuthentication” property you have but maybe you should add to this the AllowAnonymous check so that attribute can be used.

    private static bool SkipAuthorization(HttpActionContext actionContext)
    {
    Contract.Assert(actionContext != null);

    return actionContext.ActionDescriptor.GetCustomAttributes().Any()
    || actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes().Any();
    }

  • Rémy Blättler says on May 23rd, 2013 at 3:06 am :

    Yes, if you omit the RequireAuthentication attribute (you can define it on method or class level) it will allow for anonymous users.

  • James says on June 24th, 2013 at 5:31 pm :

    Thanks for your post! Just used this to secure my API and it works well.

  • MAttias Bomelin says on September 30th, 2013 at 7:40 pm :

    Is it possible to use it in combination with Authorize(Users=”xx”) or Authorize(Roles=”yy”) to only allow certain users/roles access to one action?

  • Rémy Blättler says on October 1st, 2013 at 4:16 am :

    That is currently not included. You would have to build that yourself.

Leave a Reply

  • Topics
  • Archive
  • Subscribe
  • Facebook
  • Twitter