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 { g
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.Create a custom ActionFilter class (for OnActionExecuting):
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
namespace YourNameSpace
{
public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ClaimsIdentity claimsIdentity = HttpContext.Current.User.Identity as ClaimsIdentity;
filterContext.ActionParameters["roleId"] = int.Parse(claimsIdentity.FindFirst("RoleId").Value);
filterContext.ActionParameters["userId"] = int.Parse(claimsIdentity.GetUserId());
}
}
}
Then decorate a choice of Base Controller, Controller or Action(s) (depending on the level you want to apply the custom filter), and specify roleId and userId as Action parameters:
[CustomActionFilter]
public async Task<IHttpActionResult> getTest(int roleId, int userId, int examId, int userTestId, int retrieve)
{
// roleId and userId available to use here
// Your code here
}
Hopefully that should do it.