7
votes

In our asp.net mvc/web api project, we want to customize the authorization using AuthorizeAttribute. We have noticed that there are two different AuthorizeAttribute, one in System.Web.MVC namespace for MVC and the other in System.Net.Http namespace for web api.

It works in MVC, our code like this:

public class MyPrincipal : IPrincipal
{
    //some custom properties
    public bool IsValid()
    {
        //custom authentication logic
    }

    private IIdentity identity;
    public IIdentity Identity
    {
        get { return this.identity; }
    }

    public bool IsInRole(string role)
    {
        return true;
    }
}

//override AuthorizeCore
public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        MyPrincipal user = new MyPrincipal();

        if (user.isValid())
        {
            httpContext.User = user;
        }
        else
        {
            httpContext.Response.Redirect("~/Common/NoAuthorize", true);
        }
    }
}

[MyAuthorizeAttribute]
public class BaseMyController : Controller
{
    protected virtual new MyPrincipal User
    {
        get { return HttpContext.User as MyPrincipal; }
    }
}

Then in MVC controller,we can get the user information via MyPrincipal user property.

However, when we start to use the same way in web api, we found that the web api has no HttpContext property and in System.Web.Http.AuthorizeAttribute, the method to be override accepts a HttpActionContext argument, it also has no HttpContext property or some where else we can set the MyPrincipal instance.

I notice that the System.Web.Http.AuthorizeAttribute summary says

Specifies the authorization filter that verifies the request's IPrincipal

It seems that there is some other way to set the IPrincipal instance.

I have no idea about it, any good advice? By the way, why does the asp.net web api controller have no HttpContext? Is there any design pattern about it?

The related questions ASP.NET MVC - Set custom IIdentity or IPrincipal

4

4 Answers

9
votes

I implemented something as a proof of concept following mostly this: Authentication Filters in ASP.NET Web API 2

For Web API you can create an Attribute, IAuthenticationFilter. If I remember rightly, you can add it as a filter to the global filters in WebApiConfig

config.Filters.Add(new YourAuthenticationAttribute());

Or you can use it as an attribute on the api controller/ method.

You can then implement AuthenticateAsync, get the request's authorization header, check the scheme, and validate the parameter, and if all is valid, set the principal.

I think the idea is that you can add multiple of these filters in a chain and they can all authenticate against a specific set of requirements, like the scheme they look for, and somewhere in the chain the principal gets set, or a challenge is returned.

public class YourAuthenticationAttribute : Attribute, IAuthenticationFilter
{

public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;

if (request.Headers.Authorization != null &&
    request.Headers.Authorization.Scheme.Equals("yourScheme", StringComparison.OrdinalIgnoreCase))
    {
        // get the value sent with the header.
        string authParam = request.Headers.Authorization.Parameter;

        // do some validation on the parameter provided...
        // if it's all valid, create a principal with claims:


    List<Claim> claims = new List<Claim>()
    {
        new Claim(ClaimTypes.Name, "Eddie Admin"),
        new Claim(ClaimTypes.Role, "Admin"),
        // new Claim(ClaimTypes.Role, "Delete"),
    };

    // create an identity with the valid claims.
    ClaimsIdentity identity = new ClaimsIdentity(claims, "yourScheme");

    // set the context principal.
    context.Principal = new ClaimsPrincipal(new[] { identity });

When creating the principal you can apply claims and these are checked against the normal authorize attribute. e.g.

[Authorize(Roles = "Admin")]

I haven't used it beyond doing this, but hopefully this points you in the right direction.

1
votes

This AuthorizeAttribute implementation worked for me. It's designed for Http Basic Auth but obviously I want to get the User.Identity.IsAuthenticated and User.Identity.Name from inside a ApiController too and this works:

public class ApiAuthAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        var session = (ISession)actionContext.Request.GetDependencyScope().GetService(typeof(ISession));

        if (actionContext.Request.Headers.Authorization != null)
        {
            var authConcat = Encoding.UTF8.GetString(Convert.FromBase64String(actionContext.Request.Headers.Authorization.Parameter));
            var email = authConcat.Split(':')[0];
            var password = authConcat.Split(':')[1];

            var user = session.Query<UserAccount>().SingleOrDefault(u => u.Email == email);
            if (user != null && user.IsAuthenticated(password))
            {
                actionContext.ControllerContext.RequestContext.Principal = new GenericPrincipal(new GenericIdentity(user.Email), new string[] { });
                return;     // and continue with controller
            }
        }

        actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
    }
}
0
votes

I remember that in WebApi I could only use ControllerContext. But I'm not sure exactly why. From What I read, HttpContext is thread static, but everything in WebApi is more or less async. Take a look at this topic, maybe it will answer some questions.

0
votes

Just extend your IPrincipal.

public static class IPrincipalExtension
{
   public static bool IsValid(this IPrincipal p)
   {
       // your custom validation
       return true;
   }
}

Now reference your namespace and use like this.

public class MyAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var valid = actionContext.RequestContext.Principal.IsValid(); // this will return boolean
        // your code here


    }
}