MVC middleware check attribute on controller method - c#

I am using asp.net core mvc. Next to default authentification I have added very specific authorization which is done by using ResultFilterAttribute attribute.
In future, to make sure that developers are going to specify permissions for each controller method I would like to check if the attribute is set for method, before action is executed.
Can it be done in MVC middleware. Or maybe there is better approach?

Thanks to Christian for mentioning ControllerFactory. It was right approach in my case.
public class MyControllerFactory : DefaultControllerFactory
{
public MyControllerFactory (IControllerActivator controllerActivator, IEnumerable<IControllerPropertyActivator> propertyActivators)
: base(controllerActivator, propertyActivators)
{
}
public override object CreateController(ControllerContext context)
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
var isDefined = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
.Any(a => a.GetType().Equals(typeof(PermissionFilterAttribute)));
if (!isDefined)
{
throw new NotImplementedException();
}
return base.CreateController(context);
}
}
At at Startup.cs need to tell mvc to use MyControllerFactory when resolving IControllerFactory interface.
services.AddSingleton<IControllerFactory, MyControllerFactory>();

Related

Conditionally Ignore Authorize .NET Core 3.1

I am migrating a web API from .net Framework to .net Core. The old version was able to ignore the Authorize Attribute on the controller if the app was running on a private server. Here is the code for that. I know .net core 3.1 does not offer Custom AuthorizeAttributes. That is not my question.
// A custom AuthroizeAttribute
public class ConditionalAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext httpContext)
{
if (environment_private())
return true;
else
return base.IsAuthorized(httpContext);
}
private bool environment_private()
{
// code that will tell you if you are in your dev environment or not
return Properties.Settings.Default.PrivateServer;
}
}
// How it was called from the controller
[ConditionalAuthorize(Roles = "MyRole")]
[Route(...)]
// More code for controller
I just need a simple way to authorized all requests when running the project on our private server (which is determined by a variable in appSettings.json). I have tried policies, but I face the following difficulties:
1) I couldn't pass the variable in the configuration from the controller to a parameterized authorize attribute.
2) I couldn't inject the configuration into a parameterized authorize attribute.
This effectively eliminated my ability to follow this guide in any way: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.2
This leads to my question: How can I use a value from appSettings.json to override whether the request checks a role or not?
After a lot of research, I found a way to do it using TypeFilterAttribute. Essentially, it's the same way of doing it (using a custom attribute to filter all requests and check the condition within the custom attribute) except I used .net Core-supported methods.
In case you are trying to solve this same issue, here are the exact steps to my solution.
Add two files, "YourAttributeNameAttribute.cs" and "YourFilterNameFilter.cs".
In the "YourAttributeNameAttribute.cs" file, the code is as follows:
public class YourAttributeNameAttribute : TypeFilterAttribute
{
public YourAttributeNameAttribute(string role) : base(typeof(YourFilterNameFilter))
{
Arguments = new object[] { role };
}
}
The code in "YourFilterNameFilter.cs":
public class YourFilterNameFilter : IAuthorizationFilter
{
private readonly string Role;
public YourFilterNameFilter(string role)
{
Role = role;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>();
// If private server, ignore roles
if (private_server_logic_here)
return;
var user = context.HttpContext.User;
// Check role if on public server
if (!user.IsInRole(Role))
{
context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Unauthorized);
return;
}
}
}
The code for the controller:
[YourAttributeName("role_name")]
[Route("api/my_route")]
[HttpGet]

IAuthorizationFilter not called every time

We use an IAuthorizationFilter class to filter all request, and check if custom user claims are still present (multi-tenant app) in the authentication cookie. These information are essentials for the rest of the application. If these informations are not present, we redirect to the Login page.
public class TokenAuthorizationFilter : IAuthorizationFilter, IAsyncAuthorizationFilter
{
public TokenAuthorizationFilter()
{
// Some dependency injection ...
}
public void OnAuthorization(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
CheckToken(context);
}
public Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
return CheckToken(context);
}
}
And we register our filter like this
services.AddMvc(config =>
{
config.Filters.Add(typeof(TokenAuthorizationFilter));
});
And the controller's action that I want to access is very simple :
[Authorize(Policy = "TokenValid")]
public class HomeController : AjaxBaseController
{
public IActionResult Index()
{
return View();
}
}
We even not reached the Policy of our AuthorizeAttribute. As I can see in the stacktrace, Identity is attempting to create a Microsoft.AspNet.Identity.SignInManager somewhere in the middleware after checking for a CookieAuthenticationOptions, I assumed that he's attempting to re-login the user, but it's not checking for my Filter ? Login is very special in our application, so I don't want to let Identity log automatically our user. I can reproduced this issue when the authentication cookie expired.
Any ideas ? Thanks !
You also need to make TokenAuthorizationFilter inherit from AuthorizeAttribute for an authorization filter, and rename it as TokenAuthorizationFilterAttribute.
This will become an attribute that you will be able to call with [TokenAuthorizationFilter]:
[TokenAuthorizationFilter]
public class HomeController : AjaxBaseController
{
public IActionResult Index()
{
return View();
}
}
Be careful when implementing both IAuthorizationFilter and IAsyncAuthorizationFilter, as ASP.NET Core will only call the async method in this case: if you do not need any async call, then only implement the IAuthorizationFilter interface.
Also, if you keep to register the filter like this:
services.AddMvc(config =>
{
config.Filters.Add(typeof(TokenAuthorizationFilter));
});
You will notice that the filter will be called for every action, as it will force the authorization filter to be called every time, so in this case you do not need to add the attribute on top of your action.
Finally I found out the problem. Every 30 minutes, Identity is trying to validate the user through SecurityStamp validation, and that's making the app crash because it needed a database connection which doesn't exists at the time of the validation. We've desactive this validation in our startup by reimplementing the OnValidatePrincipal :
options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents { OnValidatePrincipal = (context) => Task.FromResult(0) };

Inject service into Action Filter

I am trying to inject a service into my action filter but I am not getting the required service injected in the constructor. Here is what I have:
public class EnsureUserLoggedIn : ActionFilterAttribute
{
private readonly ISessionService _sessionService;
public EnsureUserLoggedIn()
{
// I was unable able to remove the default ctor
// because of compilation error while using the
// attribute in my controller
}
public EnsureUserLoggedIn(ISessionService sessionService)
{
_sessionService = sessionService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Problem: _sessionService is null here
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
}
And I am decorating my controller like so:
[Route("api/issues"), EnsureUserLoggedIn]
public class IssueController : Controller
{
}
Startup.cs
services.AddScoped<ISessionService, SessionService>();
Using these articles as reference:
ASP.NET Core Action Filters
Action filters, service filters and type filters in ASP.NET 5 and MVC 6
Using the filter as a ServiceFilter
Because the filter will be used as a ServiceType, it needs to be registered with the framework IoC. If the action filters were used directly, this would not be required.
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddScoped<ISessionService, SessionService>();
services.AddScoped<EnsureUserLoggedIn>();
...
}
Custom filters are added to the MVC controller method and the controller class using the ServiceFilter attribute like so:
[ServiceFilter(typeof(EnsureUserLoggedIn))]
[Route("api/issues")]
public class IssueController : Controller {
// GET: api/issues
[HttpGet]
[ServiceFilter(typeof(EnsureUserLoggedIn))]
public IEnumerable<string> Get(){...}
}
There were other examples of
Using the filter as a global filter
Using the filter with base controllers
Using the filter with an order
Take a look, give them a try and see if that resolves your issue.
Hope this helps.
Global filters
You need to implement IFilterFactory:
public class AuthorizationFilterFactory : IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// manually find and inject necessary dependencies.
var context = (IMyContext)serviceProvider.GetService(typeof(IMyContext));
return new AuthorizationFilter(context);
}
}
In Startup class instead of registering an actual filter you register your filter factory:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizationFilterFactory());
});
One more way for resolving this problem. You can get your service via Context as in the following code:
public override void OnActionExecuting(ActionExecutingContext context)
{
_sessionService = context.HttpContext.RequestServices.GetService<ISessionService>();
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
Please note that you have to register this service in Startup.cs
services.AddTransient<ISessionService, SessionService>();
Example
private ILoginService _loginService;
public override void OnActionExecuting(ActionExecutingContext context)
{
_loginService = (ILoginService)context.HttpContext.RequestServices.GetService(typeof(ILoginService));
}
Hope it helps.
After reading this article ASP.NET Core - Real-World ASP.NET Core MVC Filters (Aug 2016) I implemented it like this:
In Starup.cs / ConfigureServices:
services.AddScoped<MyService>();
In MyFilterAttribute.cs:
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof (MyFilterAttributeImpl))
{
}
private class MyFilterAttributeImpl : IActionFilter
{
private readonly MyService _sv;
public MyFilterAttributeImpl(MyService sv)
{
_sv = sv;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_sv.MyServiceMethod1();
}
public void OnActionExecuted(ActionExecutedContext context)
{
_sv.MyServiceMethod2();
}
}
}
In MyFooController.cs :
[MyFilter]
public IActionResult MyAction()
{
}
Edit: Passing arguments like [MyFilter("Something")] can be done using the Arguments property of the TypeFilterAttribute class: How do I add a parameter to an action filter in asp.net? (rboe's code also shows how to inject things (the same way))
While the question implicitly refers to "filters via attributes", it is still worth highlighting that adding filters "globally by type" supports DI out-of-the-box:
[For global filters added by type] any constructor dependencies will be populated by dependency injection (DI). Adding a filter by type is equivalent to filters.Add(new TypeFilterAttribute(typeof(MyFilter))).
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
With regards to attribute-based filters:
Filters that are implemented as attributes and added directly to controller classes or action methods cannot have constructor dependencies provided by dependency injection (DI). This is because attributes must have their constructor parameters supplied where they're applied. This is a limitation of how attributes work.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
However, as mentioned in the previous answers to the OP, there are ways of indirection that can be used to achieve DI. For the sake of completeness, here are the links to the official docs:
ServiceFilterAttribute
TypeFilterAttribute
IFilterFactory implemented on your attribute

How to use dependency injection with an attribute?

In an MVC project I'm creating I have the following RequirePermissionAttribute that gets put on any action that needs specific permissions (it's been simplified for this example):
public class RequirePermissionAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public Operation Permissions { get; set; }
public RequirePermissionAttribute() { }
public RequirePermissionAttribute(Operation permissions)
{
this.Permissions = permissions;
}
public bool AuthorizeCore(HttpContextBase httpContext)
{
IAuthorizationService authServ = new ASPNETAuthorizationService();
return authServ.Authorize(httpContext);
}
public void OnAuthorization(AuthorizationContext filterContext)
{
Enforce.ArgNotNull(filterContext);
if (this.AuthorizeCore(filterContext.HttpContext))
{
// code snipped.
}
else
{
// code snipped.
}
}
}
So the problem obviously with this is that my authorize attribute has a dependency on the ASPNETAuthorizationService that I created. I can't go the constructor way since attributes are compile-time checked.
One thing to mention, I'm using my own little IoC that I made and it doesn't have support for property injection (yet). Of course, if I did go the property injection route, I'd have to add support for it (which I'd have to do some research on).
What's the best way to inject something into an attribute class?
What's the best way to inject something into an attribute class?
Strictly speaking, we cannot use dependency injection to inject a dependency into an attribute. Attributes are for metadata not behavior. [AttributeSpecification()] encourages this by forbidding reference types as arguments.
What you're probably looking for is to use an attribute and a filter together, and then to inject dependencies into the filter. The attribute adds metadata, which determines whether to apply the filter, and the filter receives the injected dependencies.
How to use dependency injection with an attribute?
There are very few reasons to do this.
That said, if you're intent on injecting into an attribute, you can use the ASP.NET Core MVC IApplicationModelProvider. The framework passes dependencies into the provider's constructor, and the provider can pass dependencies to the attribute's properties or methods.
In your Startup, register your provider.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.TryAddEnumerable(ServiceDescriptor.Transient
<IApplicationModelProvider, MyApplicationModelProvider>());
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
Use constructor injection in the provider, and pass those dependencies to the attribute.
using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Routing;
public class MyApplicationModelProvider : IApplicationModelProvider
{
private IUrlHelperFactory _urlHelperFactory;
// constructor injection
public MyApplicationModelProvider(IUrlHelperFactory urlHelperFactory)
{
_urlHelperFactory = urlHelperFactory;
}
public int Order { get { return -1000 + 10; } }
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
foreach (var controllerModel in context.Result.Controllers)
{
// pass the depencency to controller attibutes
controllerModel.Attributes
.OfType<MyAttribute>().ToList()
.ForEach(a => a.UrlHelperFactory = _urlHelperFactory);
// pass the dependency to action attributes
controllerModel.Actions.SelectMany(a => a.Attributes)
.OfType<MyAttribute>().ToList()
.ForEach(a => a.UrlHelperFactory = _urlHelperFactory);
}
}
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
// intentionally empty
}
}
Create an attribute with public setters that can receive dependencies.
using System;
using Microsoft.AspNetCore.Mvc.Routing;
public sealed class MyAttribute : Attribute
{
private string _someParameter;
public IUrlHelperFactory UrlHelperFactory { get; set; }
public MyAttribute(string someParameter)
{
_someParameter = someParameter;
}
}
Apply the attribute to a controller or an action.
using Microsoft.AspNetCore.Mvc;
[Route("api/[controller]")]
[MyAttribute("SomeArgument")]
public class ValuesController : Controller
{
[HttpGet]
[MyAttribute("AnotherArgument")]
public string Get()
{
return "Foobar";
}
}
The above demonstrates one way, for the rare use case, that you can inject dependencies into an attribute. If you figure out a valid reason to do this, please post it in the comments.
I originally thought this was not possible, but I stand corrected. Here's an example with Ninject:
http://codeclimber.net.nz/archive/2009/02/10/how-to-use-ninject-to-inject-dependencies-into-asp.net-mvc.aspx
Update 2016-10-13
This is a pretty old question by now, and frameworks have changed quite a bit. Ninject now allows you to add bindings to specific filters based on the presence of specific attributes, with code like this:
// LogFilter is applied to controllers that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Controller, 0)
.WhenControllerHas<LogAttribute>()
.WithConstructorArgument("logLevel", Level.Info);
// LogFilter is applied to actions that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Action, 0)
.WhenActionHas<LogAttribute>()
.WithConstructorArgument("logLevel", Level.Info);
// LogFilter is applied to all actions of the HomeController
this.BindFilter<LogFilter>(FilterScope.Action, 0)
.WhenControllerTypeIs<HomeController>()
.WithConstructorArgument("logLevel", Level.Info);
// LogFilter is applied to all Index actions
this.BindFilter(FilterScope.Action, 0)
.When((controllerContext, actionDescriptor) =>
actionDescriptor.ActionName == "Index")
.WithConstructorArgument("logLevel", Level.Info);
This is in keeping with the principle, argued by Mark Seeman and by the author of Simple Injector, which is that you should keep the logic of your action filter separate from the custom attribute class.
MVC 5 and 6 also make it far easier to inject values into attributes than it used to be. Still, separating your action filter from your attribute is really the best approach to take.

Why is User (as in User.Identity.Name) null in my abstract base controller?

I was asking a related question but messed the title up and no-one would understand it. Since I am able now to ask the question more precisely, I decided to reformulate it in a new question and close the old one. Sorry for that.
So what I want to do is passing data (my custom user's nickname as stored in the db) to the LoginUserControl. This login gets rendered from the master page via Html.RenderPartial(), so what I really need to do is making sure that, say ViewData["UserNickname"] is present on every call. But I don't want to populate ViewData["UserNickname"] in each and every action of every controller, so I decided to use this approach and create an abstract base controller which will do the work for me, like so:
public abstract class ApplicationController : Controller
{
private IUserRepository _repUser;
public ApplicationController()
{
_repUser = RepositoryFactory.getUserRepository();
var loggedInUser = _repUser.FindById(User.Identity.Name); //Problem!
ViewData["LoggedInUser"] = loggedInUser;
}
}
This way, whatever my deriving Controller does, the user information will already be present.
So far, so good. Now for the problem:
I can't call User.Identity.Name because User is already null. This is not the case in all of my deriving controllers, so this is specific for the abstract base controller.
I am setting the User.Identity.Name via FormsAuthentication at another place in the code, but I think this can't be the problem - afaik User.Identity.Name can be null, but not User itself.
It looks to me like the HttpContext is not available (since also null ;-) and that I am missing a simple yet important point here. Can anyone give me some hints? I would really appreciate it.
The answer to this problem is actually quite simple. I can't execute the code from within the constructor for reasons pointed out by Raimond, but I can do it outside the constructor.
So what I did was overriding onActionExecuting() in the base controller class (I created a custom Attribute for it, but just overriding the method should also work) and then do my user lookup from there.
Now it works as expected and I have no repeated code.
The User property is not assigned until after the Controller has been instantiated, but you can gain early access from your constructor with:
System.Web.HttpContext.Current.User
My guess would be that the Controller's base constructor is not filling in the User, but that it is only known later when the ControllerContext is set for the Controller. You should check this in the documentation about the lifecycle of an MVC application, (the one here will probably do, although it might be a bit out of date since it's for the preview version), or just check the source code of MVC.
from the code that I have of MVC (also a preview version, but that should be fine):
(In Controller)
public IPrincipal User {
get {
return HttpContext == null ? null : HttpContext.User;
}
}
...
public HttpContextBase HttpContext {
get {
return ControllerContext == null ? null : ControllerContext.HttpContext;
}
}
I don't see en an implementation of a default constructor in the code.
That would prove that the ControllerContext is null at the time of construction.
So you should execute your code somewhere else.
Can you grab this using something like:
HttpContext currentContext = HttpContext.Current;
string userName = currentContext.User.Identity.Name;
Or is the HttpContext always empty??
Could you set the httpContext through the constructor of the abstract class? and use it this way?
Thanks Raimond. I was too tired to see the obvious.
#Keeney: Yes the context is always null. Raimond pointed out why. Thanks anyway, I didn't see why too :-)
My current working solution (albeit not what I wanted) is a Attribute that I use to decorate all my controller actions. Here is the implementation:
public class MasterPageDataAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
IUserRepository _repUser = RepositoryFactory.getUserRepository();
IPrincipal siteUser = filterContext.Controller.ControllerContext.HttpContext.User;
User loggedInUser = null;
if (siteUser == null || siteUser.Identity.Name == null)
{
//do nothing
}
else
{
loggedInUser = _repUser.findUserById(siteUser.Identity.Name);
}
filterContext.Controller.ViewData["LoggedInUser"] = loggedInUser ?? new User { Nickname = "Guest" };
}
}
I will be looking into how to get that code executed in a way that follows the DRY principle, since using attributes for that definitely means repeating oneself. Maybe some sort of interceptor (interesting idea) or hook might help.
Cheers for that.
I am doing this in a basecontroller implementation and it works as expected.
public abstract class BaseController : Controller
{
public bool LoggedOn
{
get { return User.Identity.IsAuthenticated; }
}
}
This always returns true or false for me so User != null
to Masterfu:
I did something similiar with your help, wish that can help latter visitors.
In my case, i need to create reposiotry of controllers for different users, yet in the constructor of controllers, (principal)User is not ready. So i created a attribute for controllers:
[CreateRepositoryByUser]
public class MFCController : Controller
{
protected MFCRepository _repository
{
get { return ViewData["repository"] as MFCRepository; }
}
...
the _repository, indeed, is not a private variable of controller, but somethign create by the attribute:
public class CreateRepositoryByUser : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CreateRepository(filterContext);
}
public static void CreateRepository(ActionExecutingContext filterContext)
{
if (filterContext.Controller.ViewData["repository"] == null)
{
filterContext.Controller.ViewData["repository"] =
MFCRepository.CreateMFCRepository(filterContext.Controller.ControllerContext.HttpContext.User);
}
}
}
I put codes of creating the repository in a seperate method, in case of that other attributes may want to use (principal)User before this attribute being triggered.
Calling from a constructor is too soon in the MVC pipeline.
Moving code to OnAuthorization, you get authorized user in a parameter. Worked for me!
From your example I would do something like this:
public abstract class ApplicationController : Controller {
private IUserRepository _repUser;
protected override void OnAuthorization(AuthorizationContext filterContext)
{
_repUser = RepositoryFactory.getUserRepository();
var loggedInUser = _repUser.FindById(filterContext.HttpContext.User.Identity.Name); //Problem!
ViewData["LoggedInUser"] = loggedInUser;
}
}
Inject IPrincipal if you need User in the constructor.
// startup.cs
// Inject IPrincipal
services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
Then add as IPrincipal in your constructor. Note that it is guaranteed to be ClaimsPrincipal with ASPNET - because that's what HttpContext.User is.
Similar question
Select Project -> press F4 -> anonymous login -> false | windows authentication - > True

Categories

Resources