Basic Http Authorization for Web API in MVC 4 Beta

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




Ähnliche Beiträge


23 Kommentare zu “Basic Http Authorization for Web API in MVC 4 Beta”



  • Ivan am 9. June 2012 11:31 am Uhr

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


  • Chris am 24. June 2012 4:25 pm Uhr

    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 am 25. June 2012 3:19 am Uhr

    Thanks for your hint. I forgot to mention that.


  • george am 3. July 2012 5:37 am Uhr

    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 am 3. July 2012 5:41 am Uhr

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

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


  • george am 9. July 2012 2:43 am Uhr

    I had an unrelated problem. Works great.


  • Sam am 19. July 2012 12:15 pm Uhr

    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 am 23. July 2012 9:38 pm Uhr

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


  • Sam am 24. July 2012 10:56 am Uhr

    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 am 24. July 2012 2:05 pm Uhr

    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 am 30. July 2012 5:56 pm Uhr

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


  • Lelala am 4. April 2013 11:03 am Uhr

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


  • Ajosh am 3. May 2013 2:36 pm Uhr

    Thanks a lot. This helped me a lot.


  • xiaobao am 21. May 2013 6:16 pm Uhr

    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 am 22. May 2013 3:42 am Uhr

    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 am 22. May 2013 12:48 pm Uhr

    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 am 23. May 2013 3:06 am Uhr

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


  • James am 24. June 2013 5:31 pm Uhr

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


  • MAttias Bomelin am 30. September 2013 7:40 pm Uhr

    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 am 1. October 2013 4:16 am Uhr

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


  • Chris Nevill am 6. July 2014 7:31 am Uhr

    I’m starting from the ASP.NET Web API template in Visual Studio 2013 Update 2.

    The BasicAuthorization Attribute is just completely ignored and control passes into the action without being authorized.

    I’ve followed the other recommendations here to ensure only Annonymous Auth is enabled in IIS, but it makes no difference. I definitely have the attribute marked on the action in my controller. Is there anything else I have to do?


  • Chris Nevill am 6. July 2014 7:33 am Uhr

    Ps your spam protection doesn’t make it clear you need to enter digits. I was asked the sum of one plus eight – so I entered “nine”. It took me about 6 attempts before I realised it was looking for ints rather than a string!


  • Chris Nevill am 6. July 2014 8:10 am Uhr

    Ah I’ve figured it out. I was actually placing the authentication on an MVC controller (Home) rather than a WEB API controller (Values).

    For an MVC controller you would need to use System.Web.MVC.AuthorizeAttribute instead of System.Web.Http.AuthorizeAttribute.

    Thanks for the blog.


Leave a Reply

Your email address will not be published. Required fields are marked *



*