Right now I am doing this to get the information I need:
In my base controller:
public int roleId { get; private set; } public int userId { get; private set; } public void setUserAndRole() { ClaimsIdentity claimsIdentity; var httpContext = HttpContext.Current; claimsIdentity = httpContext.User.Identity as ClaimsIdentity; roleId = Int32.Parse(claimsIdentity.FindFirst("RoleId").Value); userId = Int32.Parse(User.Identity.GetUserId()); }
In my controller methods:
public async Task<IHttpActionResult> getTest(int examId, int userTestId, int retrieve) { setUserAndRole();
I wanted the roleId and userId to be available and populated in the constructor of my class but from what I understand the constructor fires before authorization information is available.
Can someone tell me how I could do this with an Action Filter? Ideally I would like the Action Filter to be at the controller level but if not then could it be done at the method level.
I am hoping for some good advice and suggestions. Thank you
Update to show System.Web.Http
#region Assembly System.Web.Http, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 // C:\H\server\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll #endregion using System.Threading; using System.Threading.Tasks; using System.Web.Http.Controllers; namespace System.Web.Http.Filters { // // Summary: // Represents the base class for all action-filter attributes. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IFilter { // // Summary: // Initializes a new instance of the System.Web.Http.Filters.ActionFilterAttribute // class. protected ActionFilterAttribute(); // // Summary: // Occurs after the action method is invoked. // // Parameters: // actionExecutedContext: // The action executed context. public virtual void OnActionExecuted(HttpActionExecutedContext actionExecutedContext); public virtual Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken); // // Summary: // Occurs before the action method is invoked. // // Parameters: // actionContext: // The action context. public virtual void OnActionExecuting(HttpActionContext actionContext); public virtual Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken); } }
Using an Action Filter An action filter is an attribute. You can apply most action filters to either an individual controller action or an entire controller. For example, the Data controller in Listing 1 exposes an action named Index() that returns the current time.
In case your controller action has multiple arguments and in your filter you want to select the one that is bound via [FromBody] , then you can use reflection to do the following: public void OnActionExecuting(ActionExecutingContext context) { foreach (ControllerParameterDescriptor param in context. ActionDescriptor.
Filters execute in this order: Authorization filters. Action filters. Response/Result filters.
Based on your method signature (and later comments below) the code assumes that you are using Web API and not MVC although this could easily be changed for MVC as well.
I do want to specify that if you look purely at the requirements its how can I create a maintainable piece of code that is reused. In this case the code gets claims based information and injects it into your controllers. The fact that you are asking for a Filter is a technical requirement but I am also going to present a solution that does not use a Filter but an IoC instead which adds some flexibility (IMHO).
Some Tips
System.Web.HttpContext.Current
. It is very hard to unit test code that makes use of this. Mvc and Web API have a common abstraction called HttpContextBase
, use this when possible. If there is no other way (I have not seen this yet) then use new HttpContextWrapper(System.Web.HttpContext.Current)
and pass this instance in to what ever method/class you want to use (HttpContextWrapper
derives from HttpContextBase
).These are in no particular order. See end for a basic pro list of each solution.
UserInfo.cs
This is common code used in both solutions that I will demo below. This is a common abstraction around the properties / claims based info you want access to. This way you do not have to extend methods if you want to add access to another property but just extend the interface / class.
using System; using System.Security.Claims; using System.Web; using Microsoft.AspNet.Identity; namespace MyNamespace { public interface IUserInfo { int RoleId { get; } int UserId { get; } bool IsAuthenticated { get; } } public class WebUserInfo : IUserInfo { public int RoleId { get; set; } public int UserId { get; set; } public bool IsAuthenticated { get; set; } public WebUserInfo(HttpContextBase httpContext) { try { var claimsIdentity = httpContext.User.Identity as ClaimsIdentity; IsAuthenticated = httpContext.User.Identity.IsAuthenticated; if (claimsIdentity != null) { RoleId = Int32.Parse(claimsIdentity.FindFirst("RoleId").Value); UserId = Int32.Parse(claimsIdentity.GetUserId()); } } catch (Exception ex) { IsAuthenticated = false; UserId = -1; RoleId = -1; // log exception } } } }
This solution demos what you asked for, a reusable Web API filter that populates the claims based info.
WebApiClaimsUserFilter.cs
using System.Web; using System.Web.Http.Controllers; namespace MyNamespace { public class WebApiClaimsUserFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) { // access to the HttpContextBase instance can be done using the Properties collection MS_HttpContext var context = (HttpContextBase) actionContext.Request.Properties["MS_HttpContext"]; var user = new WebUserInfo(context); actionContext.ActionArguments["claimsUser"] = user; // key name here must match the parameter name in the methods you want to populate with this instance base.OnActionExecuting(actionContext); } } }
Now you can use this filter by applying it to your Web API methods like an attribute or at the class level. If you want access everywhere you can also add it to the WebApiConfig.cs code like so (optional).
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Filters.Add(new WebApiClaimsUserFilterAttribute()); // rest of code here } }
WebApiTestController.cs
Here how to use it in a Web API method. Note that the matching is done based on the parameter name, this has to match the name assigned in the filter actionContext.ActionArguments["claimsUser"]
. Your method will now be populated with the added instance from your filter.
using System.Web.Http; using System.Threading.Tasks; namespace MyNamespace { public class WebApiTestController : ApiController { [WebApiClaimsUserFilterAttribute] // not necessary if registered in webapiconfig.cs public async Task<IHttpActionResult> Get(IUserInfo claimsUser) { var roleId = claimsUser.RoleId; await Task.Delay(1).ConfigureAwait(true); return Ok(); } } }
Here is a wiki on Inversion of Control and a wiki on Dependency Injection. These terms, IoC and DI, are usually used interchangeably. In a nutshell you define dependencies, register them with a DI or IoC framework, and these dependency instances are then injected in your running code for you.
There are many IoC frameworks out there, I used AutoFac but you can use whatever you want. Following this method you define your injectibles once and get access to them wherever you want. Just by referencing my new interface in the constructor it will be injected with the instance at run time.
DependencyInjectionConfig.cs
using System.Reflection; using System.Web.Http; using System.Web.Mvc; using Autofac; using Autofac.Integration.Mvc; using Autofac.Integration.WebApi; namespace MyNamespace { public static class DependencyInjectionConfig { /// <summary> /// Executes all dependency injection using AutoFac /// </summary> /// <remarks>See AutoFac Documentation: https://github.com/autofac/Autofac/wiki /// Compare speed of AutoFac with other IoC frameworks: http://nareblog.wordpress.com/tag/ioc-autofac-ninject-asp-asp-net-mvc-inversion-of-control /// </remarks> public static void RegisterDependencyInjection() { var builder = new ContainerBuilder(); var config = GlobalConfiguration.Configuration; builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); builder.RegisterControllers(typeof(DependencyInjectionConfig).Assembly); builder.RegisterModule(new AutofacWebTypesModule()); // here we specify that we want to inject a WebUserInfo wherever IUserInfo is encountered (ie. in a public constructor in the Controllers) builder.RegisterType<WebUserInfo>() .As<IUserInfo>() .InstancePerRequest(); var container = builder.Build(); // For Web API config.DependencyResolver = new AutofacWebApiDependencyResolver(container); // 2 lines for MVC (not web api) var resolver = new AutofacDependencyResolver(container); DependencyResolver.SetResolver(resolver); } } }
Now we just have to call this when our application starts, this can be done in the Global.asax.cs file.
using System; using System.Web; using System.Web.Mvc; using System.Web.Routing; using System.Web.Http; namespace MyNamespace { public class Global : HttpApplication { void Application_Start(object sender, EventArgs e) { DependencyInjectionConfig.RegisterDependencyInjection(); // rest of code } } }
Now we can use it where ever we want.
WebApiTestController.cs
using System.Web.Http; using System.Threading.Tasks; namespace MyNamespace { public class WebApiTestController : ApiController { private IUserInfo _userInfo; public WebApiTestController(IUserInfo userInfo) { _userInfo = userInfo; // injected from AutoFac } public async Task<IHttpActionResult> Get() { var roleId = _userInfo.RoleId; await Task.Delay(1).ConfigureAwait(true); return Ok(); } } }
Here are the dependencies you can get from NuGet for this example.
Install-Package Autofac Install-Package Autofac.Mvc5 Install-Package Autofac.WebApi2
One more solution I thought of. You never specified why you needed the user and role id. Maybe you want to check access level in the method before proceeding. If this is the case the best solution is to not only implement a Filter but to create an override of System.Web.Http.Filters.AuthorizationFilterAttribute
. This allows you to execute an authorization check before your code even executes which is very handy if you have varying levels of access across your web api interface. The code I put together illustrates the point but you could extend it to add actual calls to a repository for checks.
WebApiAuthorizationClaimsUserFilterAttribute.cs
using System.Net; using System.Net.Http; using System.Web; using System.Web.Http.Controllers; namespace MyNamespace { public class WebApiAuthorizationClaimsUserFilterAttribute : System.Web.Http.Filters.AuthorizationFilterAttribute { // the authorized role id (again, just an example to illustrate this point. I am not advocating for hard coded identifiers in the code) public int AuthorizedRoleId { get; set; } public override void OnAuthorization(HttpActionContext actionContext) { var context = (HttpContextBase) actionContext.Request.Properties["MS_HttpContext"]; var user = new WebUserInfo(context); // check if user is authenticated, if not return Unauthorized if (!user.IsAuthenticated || user.UserId < 1) actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "User not authenticated..."); else if(user.RoleId > 0 && user.RoleId != AuthorizedRoleId) // if user is authenticated but should not have access return Forbidden actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden, "Not allowed to access..."); } } }
WebApiTestController.cs
using System.Web.Http; using System.Threading.Tasks; namespace MyNamespace { public class WebApiTestController : ApiController { [WebApiAuthorizationClaimsUserFilterAttribute(AuthorizedRoleId = 21)] // some role id public async Task<IHttpActionResult> Get(IUserInfo claimsUser) { // code will only be reached if user is authorized based on the filter await Task.Delay(1).ConfigureAwait(true); return Ok(); } } }
AuthorizationFilterAttribute
is the best way to go. You can add the code from the filter in solution #1 to this code if authorization passes, this way you still have access to the user information for other purposes in your code.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With