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
23 Kommentare zu “Basic Http Authorization for Web API in MVC 4 Beta”
Puting authentication in my rest service it has been a nightmare. Thanks a lot for your post.
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.
Thanks for your hint. I forgot to mention that.
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?
You’ve set it up like this:
[BasicHttpAuthorizeAttribute(RequireAuthentication = true)]
And this does not come through? Cause this is just basic framework functionality.
I had an unrelated problem. Works great.
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?
Not that I know of. Did you try Membership.GetUser()?
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.
Right. That should be possible where I do this:
TryGetPrincipal()
Just replace my authentication code with your solution.
[…] Basic Http Authorization for Web API in MVC 4 Beta […]
Wow, better example than the one supplied by Microsoft themselves. Thanks for shortening their version 🙂
Thanks a lot. This helped me a lot.
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?
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.
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();
}
Yes, if you omit the RequireAuthentication attribute (you can define it on method or class level) it will allow for anonymous users.
Thanks for your post! Just used this to secure my API and it works well.
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?
That is currently not included. You would have to build that yourself.
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?
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!
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.