Injecting route values based on the current Principal - c#

I've got a web api that accepts authentication through either an api key, a user access token, or a client access token. I've already written a DelegatingHandler for each case, that creates a new ClaimsPrincipal based on the given authentication details, and have confirmed that the principal is accessible within a controller action.
What I want to do now is inject either a company, a user, or a publisher into the route values, so I can create overloads on the controller for each case. What class/interface do I need to extend in order to plug into the pipeline with the following conditions:
Has access to the current principal
Has access to the route data
The action has not yet been selected
Edit
I'm not looking to sidestep routing here - I still want MVC to choose a route for me, based on the route values. I just want to add one more parameter to the route values before it chooses, and the parameter will have a different name and type depending on whether a user access token is used, or an api key is used.
I believe the subject of authentication should result in two distinct methods, because an api key can access all resources for a company, while a user access token can only access the resources they have been given permission to view.

I do not see a reason why you would want to go with a controller here. You would be sidestepping routing, a very opinionated piece of MVC. I would create middleware that runs before MVC (which is, itself, just middleware) instead.
If you're looking to affect RouteData inline, I would look into using a global IResourceFilter or IAsyncResourceFilter. Then, you can update the RouteData property on the ResourceExecutingContext based upon the conditions you specified in your question.
Any additional dependencies you need to determine how to populate the RouteData property can be injected into the resource filter's constructor as specified in the section on dependency injection.
public class SetRouteValueResourceFilter : IAsyncResourceFilter {
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) {
var company = context.HttpContext.Identity.FindFirst("Company")?.Value;
if (company != null) {
context.RouteData.Values.Add("company", company);
}
await next();
}
}
This answer is just an idea. Even though this is handled before the action logic, I'm not sure it will affect which route is selected. According to this diagram, routes are selected before filters are ran.

I managed to get this going by making a custom ValueProviderFactory, which reads values from the current principal's claims and makes them available for parameter binding:
public class ClaimsPrincipalValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
if (actionContext.RequestContext.Principal != null && actionContext.RequestContext.Principal is ClaimsPrincipal principal)
{
var pairs = principal.Claims.Select(claim => new KeyValuePair<string, string>(claim.Type, claim.Value));
return new NameValuePairsValueProvider(pairs, CultureInfo.CurrentCulture);
}
return null;
}
}
In order to use it, you can annotate the input parameters with the ValueProvider attribute:
public class FooController : ApiController
{
[HttpGet]
public void Bar([ValueProvider(typeof(ClaimsPrincipalValueProviderFactory))]ApiKey apiKey)
{
// ...
}
}
That's pretty ugly and unreadable, what I really wanted was something like the FromUri attribute, but for claims. ValueProviderAttribute and FromUriAttribute both inherit from ModelBinderAttribute, so I created a class which does the same:
/// <summary>
/// The action parameter comes from the user's claims, if the user is authorized.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class FromClaimsAttribute : ModelBinderAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return parameter.BindWithModelBinding(new ClaimsPrincipalValueProviderFactory());
}
}
Now the example method on the FooController looks a lot more readable:
[HttpGet]
public void Bar([FromClaims]ApiKey apiKey)
{
// ...
}
Update
Looks like this is still having problems with route selection and overloading, especially when some of the parameters are nullable and the value is null. I'm going to have to keep looking into this.
Update #2
I managed to simplify the value provider stuff a lot, after finding there is a built-in NameValuePairValueProvider

Related

Best way to hide a parameter in Swagger

I'm building an API where I have a couple of endpoints where I need the User ID, so after getting the idea from the most voted answer (not the accepted one) from this post: ASP.NET MVC Pass object from Custom Action Filter to Action.
I created an Action Filter that I can set up just with the [UserIdFromToken] attribute.
So I'm basically getting the User ID through this Action Filter that gets it from the authorization header this way:
[Authorize]
[UserIdFromToken]
public IActionResult Get(Guid userId /* Got from the [UserIdFromToken] filter */)
{
return Ok(_userService.Get(userId));
}
The only problem is now it's showing the parameter userId as a Required parameter (and I don't want to show it at all) in my Swagger documentation, which is obviously false, since I don't need any input beyond the authorization header.
Is there any way to get my swagger documentation cleaner, without showing this parameter?
Do you think it would be better to try another approach to get the userId such as the accepted option from the post I sent above?
I realize this is an old post, but if anyone have similar issues you can create your own action filter by extending Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter like this:
public class HideUserIdOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var scopes = context.MethodInfo
.GetCustomAttributes(true)
.OfType<UserIdFromTokenAttribute>()
.Distinct();
if (scopes.Any())
{
operation.Parameters = operation.Parameters.Where(param => param.Name != "userId").ToList();
}
}
}
Remember to add the operation filter to your swagger gen:
c.OperationFilter<HideUserIdOperationFilter>();

Global method for all Controller classes ASP.NET MVC

I have a lot of different controllers and each of them contains a lot of ActionResult methods which returns different views.
In my program you set a variable before you start using it. I store the variable in a Session.
For now, most of my Controllers, have methods with the same if-sentence.
Example:
if (Session["myVariable"] == null)
{
return RedirectToAction("/../Cons/SetVariable");
}
What i would like to is not to write this logic in all my controllers methods. Perhaps there is a way i could extend a new controller with the logic or something similar?
I hope somebody understands my problem and can help.
You can use MVC/WebAPI's filter attributes to execute the check and use context to perform the actions
public class VariableCheckerValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext
filterContext)
{
if(filterContext.HttpContext.Session["myVariable"] == null)
{
filterContext.Result = new RedirectToRouteResult("/../Cons/SetVariable"‌, filterContext.RouteData.Values);
}
}
}
Then register the action filter as a global filter, and it will automatically apply to every single action in your application.
In WebApiConfig (in case of WebAPI project), in typically the Register method, add:
config.Filters.Add(new VariableCheckerValidationAttribute());
Or, instead, apply at controller or action levels as needed.
If you do decide to go the global application, and you have an action that doesn't need it, then you would need to create an attribute to 'exception' out of the policy, and update the above attribute code to test for presence of the 'exception' attribute and then not apply the check in that case.

ASP.NET MVC - Optionally Redirect from a Base Class Method?

Consider the following block of code that reappears in many of my controller actions. (I'm chiefly concerned with the first 6 lines of the method body).
[HttpGet]
public ActionResult OptOut()
{
var user = this.SecurityPrincipal;
if (user.IsReadOnlyUser)
{
this.TempData["ViewModel"] = new AuthorizationModel { User = user };
return this.RedirectToAction("NotAuthorized", "Authorization");
}
var model = /* Elided for brevity */
return this.View(model);
}
My controllers derive from a base class, SecuredController which, in turn, derives from Controller. SecurityPrincipal is a property of SecuredController, and contains extensive Active Directory data about the current user.
In an effort to eliminate duplicate code, I'd ideally like to move the functionality contained in the if {...} block into a base class method, but I can't think of any way to do so, since the return type of the method would have to be ActionResult, resulting in something ungainly like this:
if ((var result = this.RequireReadWrite()) != null)
{
return result;
}
Can anyone suggest a way to do this, or am I simply out of luck here?
As mentioned in the comments, especially noting that security is a cross cutting concern we've suggested using MVC Action Filters to be applied in your use case and design.
Microsoft's documentation is pretty informative and there are more examples that can be found on the web on how to use MVC Filters. I'll try to provide an example, but this will be based on a lot of assumptions on your software architecture, since I simply don't have the knowledge of that.
You could create the following class:
public class SecuredFilterAttribute : AuthorizeAttribute
{
...
}
If using a Dependency Injection framework, you could inject the SecurityPrincipal service. But again I don't know the architecture of your application, so it's up to you how you create that dependency.
When overriding the AuthorizeCore, you could implement it like so:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !this.SecurityPrinciple.IsReadOnlyUser;
}
And when not authorized override the HandleUnauthorizedRequest method to redirect:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var redirectRoute = ...; //your route to redirect to an unauthorized page
filterContext.Result = new RedirectToRouteResult(redirectRoute);
//do some other things, for example, setting some tempdata information
}
Again it's up to you on how you would use this Filter. You could register it globally, or apply it on a per controller or action basis. To register it globally, in your startup:
GlobalFilters.Filters.Add(new SecuredFilterAttribute());

Resource based ASP.NET Authorization FormsAuthentication and WebApi

Using webapi with formsauthentication (i know may sound weird, but it exactly what I need).
I have a route like :
[Route("{application}/orders}"]
public IHttpActionResult Get(string application) {
var orders = OrderService.GetOrders(application); // return list of orders of applicaiton.
return Ok(orders);
}
Example scenario :
User John has permission to see orders for application "CoolApp"
User Leia has permission to see orders for application "Robin"
user Tristan doesn't have permission to see orders.
On the DB is like a Role - Resource relationship.
UserRole:
User - Role - Application
=====================================
John - OrdersManager - CoolApp
Leia - OrdersManager - Robin
Tristan - OtherRole - Robin
I want that route to check if the user has permission to get orders of that specific application.
I read this: Resource based authorization in .net and looks promising.
First thing is I'm not sure if I should extend using AuthorizeAttribute, AuthorizationFilterAttribute or IAuthorizationFilter as briefly explained here http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api unfortunately, is not of much information.
Second I read that binding happens AFTER the authorization so I don't know how to get the values from the RouteData since I have the {application} defined on the route data I guess I should be able to access it. There are few examples for MVC, but was not able to get one for web api and with object like request and context all over the actionContext object I'm not sure where to get it and how in a proper way.
And last but not least, what method of the chosen extension point(s) should I override. If it have both async and sync, should I override both ?
BONUS: Since the authorize will have a dependency on my repository, would be great to know how to inject that into the filter if that's even possible. (I'm not using any IoC container).
Overriding AuthorizationFilterAttribute would probably be the better route. What you'd want to do is pass the attributes you want to check for:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
string role;
string application
public MyAuthorizeAttribute (string role, string application){
this. role = roles;
this.application = application;
}
protected override bool IsAuthorized(HttpActionContext actionContext) {
var routeData = actionContext.ControllerContext.RouteData;
//do your checks
}
}
you can get access to routedata from the HttpActionContext in the IsAuthorized method. Now you can attach the attribute to your ApiController or Action Method.
I'd suggest using an IoC container is you want to do DI with an Web API ActionFilter

Accessing ApiController Type in DelegatingHandler

I am using Asp.Net WebAPI for a project. I am currently working on authentication and authorization.
I have a messageHandler that will check the HTTP authentication header of a request and build my identity and user profile. However, I want to annotate my controller action (or just the controller) with claims that the action may require (we have a lot of claims that a user can have, so I don't want to load them all).
e.g.:
public class MyController : ApiController
{
[LoadClaims("SomeClaim", "SomeOtherClaim", "etc")]
public string Get()
{
if (HasClaim("SomeClaim"))
return "Awesome";
return "Bummer";
}
}
Inside the authentication message handler I want to be able to look at the attributes and bring claims back from the DB based on only what is required. For that I need to know what Controller and Action I will hit based on route:
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
...
var routeData = request.GetRouteData();
object controllerName;
object actionName;
routeData.Values.TryGetValue("controller", out controllerName);
...
So I can get that. But now I need to turn this into a Type that I can reflect on, but all I have is the controller name (not even the full class name or namespace). How can I turn this into something that I can reflect on to get attributes etc?
I am looking into DefaultHttpControllerSelector to see how the WebAPI stack does it, and it seems to use HttpControllerTypeCache. This is an internal class so I can't create an instance. What is the correct way to go about getting the target controller Type?
You can get access to the type resolver yourself using the global service locator.
var controllerTypeResolver = GlobalConfiguration.Configuration.Services.GetHttpControllerTypeResolver();
var controllerTypes = controllerTypeResolver.GetControllerTypes(GlobalConfiguration.Configuration.Services.GetAssembliesResolver());
var controllerType = controllerTypes.SingleOrDefault(ct => ct.Name == string.Format("{0}Controller", controllerName));
You will probably want to do some caching of the results (like the controller selector does). But this approach should work.
But
You may be better off moving this logic into a Custom authorisation filter that sits on the controller rather than a delegating handler. Given you need to know the controller type you may as well let the ControllerSelector work normally. Perhaps, if you turned your load claims attribute into an authorization action filter attribute you could just load the claims passed in as parameters and set the principal and claims there and then?
If you are still set on the DelegatingHandler you could get the Controller Selector instance itself, which'll be way more efficient:
var controllerSelector = GlobalConfiguration.Configuration.Services.GetHttpControllerSelector();
var controllerDescriptor = controllerSelector.SelectController( request );

Categories