Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Customize ASP.NET Web API AuthorizeAttribute for Unusual Requirements

I am inheriting from System.Web.Http.AuthorizeAttribute to create a custom authorization/authentication routine to meet some unusual requirements for a web application developed using ASP.NET MVC 4. This adds security to the Web API used for Ajax calls from the web client. The requirements are:

  1. The user must logon each time they perform a transaction to verify someone else has not walked up to the workstation after someone has logged on and walked away.
  2. Roles cannot be assigned to the web service methods at program time. They must be assigned at run time so that an administrator can configure this. This information is stored in the system database.

The web client is a single page application (SPA) so the typical forms authentication does not work so well, but I am trying reuse as much of the ASP.NET security framework as I can to meet the requirements. The customized AuthorizeAttribute works great for requirement 2 on determining what roles are associated with a web service method. I accept three parameters, application name, resource name and operation to determine which roles are associated with a method.

public class DoThisController : ApiController {     [Authorize(Application = "MyApp", Resource = "DoThis", Operation = "read")]     public string GetData()     {         return "We did this.";     } } 

I override the OnAuthorization method to get the roles and authenticate the user. Since the user has to be authenticated for each transaction I reduce the back and forth chatter by performing authentication and authorization in the same step. I get the users credentials from the web client by using basic authentication which passes the encrypted credentials in the HTTP header. So my OnAuthorization method looks like this:

public override void OnAuthorization(HttpActionContext actionContext) {       string username;      string password;      if (GetUserNameAndPassword(actionContext, out username, out password))      {          if (Membership.ValidateUser(username, password))          {              FormsAuthentication.SetAuthCookie(username, false);              base.Roles = GetResourceOperationRoles();          }          else          {              FormsAuthentication.SignOut();              base.Roles = "";          }      }      else      {          FormsAuthentication.SignOut();          base.Roles = "";      }      base.OnAuthorization(actionContext);  } 

GetUserNameAndPassword retrieves the credentials from the HTTP header. I then use the Membership.ValidateUser to validate the credentials. I have a custom membership provider and role provider plugged in to hit a custom database. If the user is authenticated I then retrieve the roles for the resource and operation. From there I use the base OnAuthorization to complete the authorization process. Here is where it breaks down.

If the user is authenticated I use the standard forms authentication methods to log the user in (FormsAuthentication.SetAuthCookie) and if they fail I log them out (FormsAuthentication.SignOut). But the problem seems to be that base OnAuthorization class does not have access to Principal that is updated so that IsAuthenticated is set to the correct value. It is always one step behind. And my guess is that it is using some cached value that does not get updated until there is a round trip to the web client.

So all of this leads up to my specific question which is, is there another way to set IsAuthenticated to the correct value for the current Principal without using cookies? It seems to me that cookies do not really apply in this specific scenario where I have to authenticate every time. The reason I know IsAuthenticated is not set to the correct value is I also override the HandleUnauthorizedRequest method to this:

 protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)  {      if (((System.Web.HttpContext.Current.User).Identity).IsAuthenticated)      {          filterContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);      }      else      {          base.HandleUnauthorizedRequest(filterContext);      }  } 

This allows me to return a status code of Forbidden to the web client if the failure was because of authorization instead of authentication and it can respond accordingly.

So what is the proper way to set IsAuthenticated for the current Principle in this scenario?

like image 262
Kevin Junghans Avatar asked Sep 27 '12 20:09

Kevin Junghans


People also ask

How does authorize attribute work in Web API?

Web API provides a built-in authorization filter, AuthorizeAttribute. This filter checks whether the user is authenticated. If not, it returns HTTP status code 401 (Unauthorized), without invoking the action. We can apply the filter globally, at the controller level, or at the level of individual actions.


2 Answers

The best solution for my scenario appears to be bypass the base OnAuthorization completely. Since I have to authenticate each time cookies and caching the principle are not of much use. So here is the solution I came up with:

public override void OnAuthorization(HttpActionContext actionContext) {     string username;     string password;      if (GetUserNameAndPassword(actionContext, out username, out password))     {         if (Membership.ValidateUser(username, password))         {             if (!isUserAuthorized(username))                 actionContext.Response =                      new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);         }         else         {             actionContext.Response =                  new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);         }     }     else     {         actionContext.Response =              new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);     } } 

I developed my own method for validating the roles called isUserAuthorized and I am not using the base OnAuthorization any more since it checks the current Principle to see if it isAuthenticated. IsAuthenticated only allows gets so I am not sure how else to set it, and I do not seem to need the current Principle. Tested this out and it works fine.

Still interested if anyone has a better solution or can see any issues with this this one.

like image 179
Kevin Junghans Avatar answered Oct 08 '22 06:10

Kevin Junghans


To add to the already accepted answer: Checking current sourcecode (aspnetwebstack.codeplex.com) for System.Web.Http.AuthorizeAttribute, it looks like the documentation is out of date. Base OnAuthorization() just calls/checks private static SkipAuthorization() (which just checks if AllowAnonymousAttribute is used in context to bypass the rest of the authentication check). Then, if not skipped, OnAuthorization() calls public IsAuthorized() and if that call fails, it then calls protected virtual HandleUnauthorizedRequest(). And that's all it does...

public override void OnAuthorization(HttpActionContext actionContext) {     if (actionContext == null)     {         throw Error.ArgumentNull("actionContext");     }      if (SkipAuthorization(actionContext))     {         return;     }      if (!IsAuthorized(actionContext))     {         HandleUnauthorizedRequest(actionContext);     } } 

Looking inside IsAuthorized(), that's where Principle is checked against roles and users. So, overriding IsAuthorized() with what you have above instead of OnAuthorization() would be the way to go. Then again, you'd still have to probably override either OnAuthorization() or HandleUnauthorizedRequest() anyway to decide when to return a 401 vs a 403 response.

like image 21
bob-the-destroyer Avatar answered Oct 08 '22 06:10

bob-the-destroyer