In our asp.net mvc/web api project, we want to customize the authorization using AuthorizeAttribute
. We have noticed that there are two different AuthorizeAtt
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.
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
}
}
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);
}
}
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.