I can't find a simple way to do something that should be quite common.
I have Razor Pages and I want to add some authorization logic to ensure that a request
https://localhost:5001/admin/subscriptions/123`
where the 123 is any Guid is the {subscriptionId}.
I'm using a cookie based authentication, so I should have a claim Name or similar with a specific subscriptionId such as 124 for the current request.
I want to handle these subscription, so that it forbids access when the subscriptionId in the URL doesn't match a specific claim.
It's embarrassing but I don't know how to do it with AspNet Core 3.1 and Razor pages.
It simply doesn't seem to be design for these kind of scenarios. The examples I find consider that we know beforehand what the claims should be. For example in Microsoft official documentation it shows how an "age" claim can be compared to a minimum age of hardcoded magic int 21.
What If both values are request-dependant?
When I add
services
.AddAuthorization(
options =>
{
options.AddPolicy(
"SameSubscriptionIdPolicy",
policy =>
policy.RequireAssertion(context =>
{
var claims = context.User.Claims;
var subscriptionId = context.User.FindFirst(x => x.Type == ClaimTypes.Name);
// how can I access here the Request and do the comparison?
}));
});
If I access the context, I see the URL parametrized, not the real value with the request.
I see admin/subscriptions/{subscriptionId} instead a specific value such as admin/subscriptions/124
How can I access the URL query? so that I can compare it with a claim value?
UPDATE 1:
I read somewhere something here about casting like this:
context.Resource is AuthorizationFilterContext mvcContext
That does not work. Resource is a AuthorizationHandlerContext
Also I've tried the following
services
.AddAuthorization(
options =>
{
options.AddPolicy(
"SameSubscriptionIdPolicy",
policy =>
policy.RequireAssertion(context =>
{
var claims = context.User.Claims;
var subscriptionId = context.User.FindFirst(x => x.Type == ClaimTypes.Name);
var accessor = new HttpContextAccessor();
var url = accessor.HttpContext.GetRouteData();
return false;
}));
});
but HttpContext is null. Maybe by the time the policy starts, there is no request yet?
It took a while but I managed to get it working for DotNetCore 3.1+.
It turns out I needed to inject the IHttpContextAccessor service, so for that I decided not to use the inline policy func implementation and add all the pieces.
First an IAuthorizationRequirement
public class SubscriptionRequirement
: IAuthorizationRequirement
{
public string ClaimName { get; }
public SubscriptionRequirement(string claimName)
{
ClaimName = claimName;
}
}
Not to leave an empty class I decided to pass it the claim name I'm looking for, so that it's a bit more generic.
then the AuthorizationHandler capable of accessing Claims (from cookie or whatever) and the http request.
public class SubscriptionRequirementHandler
: AuthorizationHandler<SubscriptionRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public SubscriptionRequirementHandler(
IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
SubscriptionRequirement subscriptionRequirement)
{
var claims = context.User.Claims;
var subscriptionId =
context
.User
.FindFirst(x => x.Type == subscriptionRequirement.ClaimName)
.Value;
var httpContext = _httpContextAccessor.HttpContext;
var subscriptionIdInUrl = httpContext.Request.RouteValues["subscriptionId"];
if (subscriptionIdInUrl != null)
{
var isAuthorized = subscriptionId == subscriptionIdInUrl.ToString();
if (isAuthorized)
{
context.Succeed(subscriptionRequirement);
return Task.CompletedTask;
}
}
context.Fail();
return Task.CompletedTask;
}
}
To add this policy:
services
.AddAuthorization(
options =>
{
options.AddPolicy(
"SameSubscriptionIdPolicy",
policy =>
policy.Requirements.Add(new SubscriptionRequirement(ClaimTypes.Name)));
});
I can also (optionally for Razor Pages) specify that I want to apply this policy to all pages within a specific folder
services
.AddRazorPages(
options =>
{
options.Conventions.AuthorizeFolder("/admin", "SameSubscriptionIdPolicy");
options.Conventions.Add(new PageRouteTransformerConvention(new LowerCaseRoutes()));
});
and finally, very importantly, register the handler and the http
services.AddSingleton<IAuthorizationHandler, SubscriptionRequirementHandler>();
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
UPDATE 2020/06/18:
As per Kirk's comment, a nicer way to register the HttpContextAccessor is to do so as a singleton using the already available extension instead.
services.AddHttpContextAccessor();
Related
Question: Using ASP.NET Core 6 (MVC, Razor), I am struggling to correctly and efficiently implement Authorization when it comes to a user invoking certain Actions/Controllers - with CookieAuthentication.
To phrase it with more detail: I am trying to tell the .NET Core request pipeline how to distinguish between invocations on Actions/Controllers from:
An authenticated and unauthenticated user (this works)
A user having or lacking the correct privileges (being a manager or not)
A user having or lacking the correct subscription tier/edition for the action they are trying to perform. (e.g. only premium subscription users may invoke this action).
The tricky bit is - these users must all be routed to a different page depending on what is lacking
Let's assume all users concerned are authenticated (because I've got that working just fine. Unauthenticated users are supposed to be redirected to the login screen).
Next, I've used Claims (to populate a Claims Identity, which populates the ClaimsPrincipal) during the HttpContext.SignInAsync() process to determine the user's role (e.g. manager or user etc.), and decorated the appropriate Controllers/Actions with (and in combination)
[Authorize(Roles = "User")] //A user must have "user level" privileges
[Authorize(Policy = "BasicEdition")] //User must have the "basicEdition" subscription tier to access this method
Problem is I cannot get the request pipeline to redirect the user to different paths/URLs/actions depending on which one of these Authorize attributes fail.
For instance: If you're an authenticated user, and do not have "manager-level" privileges, you get redirected to accounts/ask-your-manager. However, if you're an authenticated user, and do not have "AdvancedEdition" subscription, you get redirected to subscriptions/upgrade-to-access.
I really hope I could explain this simply - Now for my code:
Startup.cs file:
services.AddAuthentication(opts =>
{ //I have even tried not using this.
opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(c =>
{
c.Cookie.Name = "AuthCookie";
c.LoginPath = "/accounts/login";
c.Cookie.IsEssential = true;
c.AccessDeniedPath = "/accounts/accessdenied";
c.SlidingExpiration = true;
c.ExpireTimeSpan = new TimeSpan(0, 2, 0);
});
services.AddAuthorization(config =>
{
//This should be the Tiers/Editions
config.AddPolicy("BasicEdition", policyBuilder =>
{
policyBuilder.UserRequireCustomClaim("BasicEdition");
});
config.AddPolicy("AdvancedEdition", policyBuilder =>
{
policyBuilder.UserRequireCustomClaim("AdvancedEdition");
});
config.AddPolicy("PremiumEdition", policyBuilder =>
{
policyBuilder.UserRequireCustomClaim("PremiumEdition");
});
});
services.AddScoped<IAuthorizationHandler, PoliciesAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, RolesAuthorizationHandler>();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromHours(12);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => context.Request.PathBase.Equals("/NeedsConsent");
});
Then the Configure() method in the same class:
app.UseCookiePolicy();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseSession();
And then simply placing the [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] attribute on the Controllers in general. That works, and will send my user directly to the login screen - this is correct and as expected.
Currently, I use this these bits to hook into the IAuthorizationHandler (according to this) to check the Role, and similarly, the Policy to interpret the subscription. However, I have discovered that unlike previous versions of the context, I cannot assign a new RedirectToAction (or anything like it) to it. I can merely tell the context whether or not the the request has succeeded or failed. How can I change this? Do I need to implement Filters or different types?
public class PoliciesAuthorizationHandler : AuthorizationHandler<CustomUserRequireClaim>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
CustomUserRequireClaim requirement)
{
if (context.User == null || !context.User.Identity.IsAuthenticated)
{
context.Fail();
return Task.CompletedTask;
}
var hasClaim = context.User.Claims.Any(c => c.Value == requirement.ClaimType);
if (hasClaim)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
context.Fail();
return Task.CompletedTask;
}
}
public class CustomUserRequireClaim : IAuthorizationRequirement
{
public string ClaimType { get; }
public CustomUserRequireClaim(string claimType)
{
ClaimType = claimType;
}
}
public class RolesAuthorizationHandler : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationHandler
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
RolesAuthorizationRequirement requirement)
{
if (context.User == null || !context.User.Identity.IsAuthenticated)
{
context.Fail();
return Task.CompletedTask;
}
var validRole = false;
if (requirement.AllowedRoles == null ||
requirement.AllowedRoles.Any() == false)
{
validRole = true;
}
else
{
var claims = context.User.Claims;
//var userName = claims.FirstOrDefault(c => c.Type == "UserId").Value;
var roles = requirement.AllowedRoles;
validRole = context.User.Claims.Any(c => c.Type == ClaimsHelper.Claim_UserRole
&& roles.Contains(c.Value));
//validRole = new Users().GetUsers().Where(p => roles.Contains(p.Role) && p.UserName == userName).Any();
}
if (validRole)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
I have followed MANY different articles on S.O as well as other forums to try and achieve my goal. Including this wonderful article that sent me down this road.
These articles (and credit to the respective OPs and those that answered them) helped me point in the right direction, however it is not exactly what I need.
How do you create a custom AuthorizeAttribute in ASP.NET Core?
(Yes I know this is a dated version of .NET Core)
How to add multiple policies in action using Authorize attribute using identity 2.0?
I have even considered combining them (kinda like multiplexing) the policies/roles but that doesn't do the exact thing I need either - since they will both send me down the same page.
How to include multiple policies
Your assistance, or even just pointing me in the right direction is truly appreciated. I'd like to gain a good understanding of what I'm building here to ensure I can optimize it later, and even expand on it.
UPDATE:
In conjunction with the accepted answer, what I also discovered was that my startup.cs file configuration was incorrect to allow the solution to work.
Inside the Startup.Configure() method, I used:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Instead of:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
You should create your own custom IAuthorizationMiddlewareResultHandler. Take a look at the documentation here: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/customizingauthorizationmiddlewareresponse?view=aspnetcore-6.0
Example for your scenario:
public class SampleAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();
public async Task HandleAsync(
RequestDelegate next,
HttpContext context,
AuthorizationPolicy policy,
PolicyAuthorizationResult authorizeResult)
{
if (authorizeResult.Forbidden)
{
if (authorizeResult.AuthorizationFailure!.FailedRequirements
.OfType<CustomUserRequireClaim>().Any())
{
context.Response.Redirect("/subscriptions/upgrade-to-access");
return;
}
if (authorizeResult.AuthorizationFailure!.FailedRequirements
.OfType<RolesAuthorizationRequirement>().Any())
{
context.Response.Redirect("/accounts/ask-your-manager");
return;
}
}
await defaultHandler.HandleAsync(next, context, policy, authorizeResult);
}
}
Program.cs:
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, SampleAuthorizationMiddlewareResultHandler>();
The handler is triggered automatically by the AuthorizationMiddleware (app.UseAuthorization()) for every controller/action. The middleware evaluates the authorization policies you have defined for the controller/action (e.g.: [Authorize(Policy = "BasicEdition")] or [Authorize(Roles = "User")]) then passes the authorization result to this handler. Inside the handler we can customize the response based on the authorization policy and the authorization result.
I'm exploring Minimal APIs in .Net 6, and trying to apply a custom Authorization Filter to the endpoint (via Attributes or Extensions).
But it seems to me, I am doing something wrong, or it's simply not designed to work in that way (and it's sad if so).
Couldn't find anything in the docs besides the default usage of [Authorize] attribute in Minimal APIs.
Here is the Filter
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
//Checking tokens
}
And if I try to apply it at Controller level, it works fine
[CustomAuthorize]
public class CustomController : ControllerBase
{
//Necessary routing
}
But if I switch to Minimap APIs notation and try to use attributes
app.MapGet("/customEndpoint",
[CustomAuthorize] async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id));
or even an extension method
app.MapGet("/customEndpoint",
async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id)).WithMetadata(new CustomAuthorizeAttribute());
It just doesn't work. The filter doesn't even being constructed.
What did I miss or did wrong?
Thx in advance
You can write a custom authorization filter for Minimal API in .NET 6.0
Here is how I tend to approach it - by using Policy-based authorization in ASP.NET Core
Step 1: Create a Requirement
A requirement implements IAuthorizationRequirement
public class AdminRoleRequirement : IAuthorizationRequirement
{
public AdminRoleRequirement(string role) => Role = role;
public string Role { get; set; }
}
Note: A requirement doesn't need to have data or properties.
Step 2: Create a Requirement Handler
A requirement handler implements AuthorizationHandler<T>
public class AdminRoleRequirementHandler : AuthorizationHandler<AdminRoleRequirement>
{
public AdminRoleRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
if (context.User.HasClaim(c => c.Value == requirement.Role))
{
context.Succeed(requirement);
}
else
{
_httpContextAccessor.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
_httpContextAccessor.HttpContext.Response.ContentType = "application/json";
await _httpContextAccessor.HttpContext.Response.WriteAsJsonAsync(new { StatusCode = StatusCodes.Status401Unauthorized, Message = "Unauthorized. Required admin role." });
await _httpContextAccessor.HttpContext.Response.CompleteAsync();
context.Fail();
}
}
private readonly IHttpContextAccessor _httpContextAccessor;
}
Note: HandleRequirementAsync method returns no value. The status of either success or failure is indicated by calling context.Succeed(IAuthorizationRequirement requirement) and passing the requirement that has been successfully validated or by calling context.Fail() to indicate AuthorizationHandlerContext.HasSucceeded will never return true, even if all requirements are met.
Step 3: Configure Your Policy in the Authorization Service
builder.Services.AddAuthorization(o =>
{
o.AddPolicy("AMIN", p => p.AddRequirements(new AdminRoleRequirement("AMIN")));
});
Step 4: Add Your Requirement Handler to DI
builder.Services.AddSingleton<IAuthorizationHandler, AdminRoleRequirementHandler>();
Step 5: Apply Policy to Endpoints
app.MapGet("/helloworld", () => "Hello World!").RequireAuthorization("AMIN");
I think you won't be able to inject action filter in minimal api, you can use 3 alternative approches.
Create a custom middleware and inject it in startup class, it would check every request and do the intended work as you filter is doing. You can put a check for the request path there if you only need to validate a specific controller/endpoint.
The second approach is you can inject httpcontext in minimal api like this, from that extract jwt token and validate that, if found not ok reject that request.
app.MapGet("/customEndpoint", async (HttpContext context, ICustomService service) =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.isNullOrEmpty(token) || <not a valid token>) return Results.Unauthorized();
// do some work
return Results.Ok(result);
});
as #Dai suggested, you can extract token in this way also
AuthenticationHeaderValue.TryParse(context.Request.Headers["Authorization"], out var parsed ) && parsed.Scheme == "BearerOrWhatever" ? parsed.Parameter : null
You can register the filter globally from startup.cs.
I have an ASP.NET Core web service running on Azure AppService. With every REST call, I check the auth0 token against my profile table to double-check they are a valid user.
Is there a way of refactoring this code out of every REST call, so it always run for every ASP.NET Core call coming in?
Here is a typical example of a REST call... (there are about 30 in all)
[Authorize]
[HttpGet("accounts/{accountId}")]
public async Task<ActionResult<ArchiveJob>> AccountGet(int accountId)
{
//--- common code repeated in every function
var profile = await RetrieveProfile();
if (profile is null)
{
return NotFound("Profile not found for this user")
}
// ... rest of function
return Ok();
}
The right way, is to use AddAuthorization to define your authorisation policies. Perhaps changing the default policy;
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireAssertion(context => {
// note; https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-6.0#access-mvc-request-context-in-handlers
if (context.Resource is HttpContext c)
{
var something = c.RequestServices.GetRequiredService < ...> ();
var profile = await something.RetrieveProfile();
if (profile != null)
return true;
}
return false;
})
.Build();
});
You can create a IAuthorizationFilter to create a custom filter in your core application so it always runs for every ASP.NET Core call coming in.
public class MyAuthAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
//check access
if (CheckPermissions())
{
//all good, add some code if you want. Or don't
}
else
{
//DENIED!
//return "ChallengeResult" to redirect to login page (for example)
context.Result = new ChallengeResult(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
you can also find detail here
I have setup a standard authentication system up in an ASP.NET Core application.
Users, Roles, RoleClaims(acting as permissions)
In Startup.cs I create a policy for each Role and each Permission. Assuming this would give me full flexibility in my Views to be able to say I want this button to show if user is part of a role that has claim DeleteCustomer or if User belongs to role Superuser.
How can I do an OR condition using the Authorize attribute. For example all throughout my site I want SuperuserRole Policy to have full permission to everything.
Over an action method let's say I have the following:
[Authorize(Policy = "EditCustomer")]
This will require that the logged in user is assigned to a role that has the claim: Edit.Customer since I am creating a policy for the claim Edit.Customer. This is all working fine, but how do I say I would like any User with the Role Superuser to be able to access EditCustomer action method. Superuser is in the database as a Role and additionally added as a policy called RequireSuperUser.
You can add OR condition in Startup.cs:
services.AddAuthorization(options => {
options.AddPolicy("EditCustomer", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c => (c.Type == "DeleteCustomer" || c.Type == "Superuser"))));
});
I was facing similar issue where I wanted only "John Doe", "Jane Doe" users to view "Ending Contracts" screen OR anyone only from "MIS" department also to be able to access the same screen. The below worked for me, where I have claim types "department" and "UserName":
services.AddAuthorization(options => {
options.AddPolicy("EndingContracts", policy =>
policy.RequireAssertion(context => context.User.HasClaim(c => (c.Type == "department" && c.Value == "MIS" ||
c.Type == "UserName" && "John Doe, Jane Doe".Contains(c.Value)))));
});
You can use a two different handlers of AuthorizationHandler with the same requirement of IAuthorizationRequirement:
public class FooOrBooRequirement : IAuthorizationRequirement { }
public class FooHandler : AuthorizationHandler<FooOrBooRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationContext context, FooOrBooRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "Foo" && c.Value == true))
{
context.Succeed(requirement);
return Task.FromResult(0);
}
}
}
public class BooHandler : AuthorizationHandler<FooOrBooRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationContext context, FooOrBooRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "Boo" && c.Value == true))
{
context.Succeed(requirement);
return Task.FromResult(0);
}
}
}
So in the AddPolicy you will have only one requirement that can be fulfilled of by the FooHandler or by the BooHandler:
services.AddAuthorization(authorizationOptions =>
{
authorizationOptions.AddPolicy(
"MustBeFooOrBoo",
policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(new FooOrBooRequirement());
});
});
Please note that the AuthorizationHandler provides a more powerful custom policy so it most relevant only for special cases that require a more complex authentication, for example if you want to determine if the user is a resource owner.
For an only simple claims-based policy I'd advice to use the #richard-mneyan's answer.
We can inject DBContext (if using EF, or user repository if not) or HttpContextAccessor to the AuthorizationHandler to achieve this.
By injecting EF Context (or user repository), we could query database to figure out if the current user is a superuser.
Or by injecting HttpContextAccessor, we can determine if the user is a superuser from that. This approach is faster than the first one but depend on whether User Context have the information needed or not.
To achieve this, assuming what you have are EditCustomerRequirement and EditCustomerAuthorizationHandler:
public class EditCustomerAuthorizationHandler: AuthorizationHandler<EditCustomerRequirement>
{
readonly AppDbContext _context;
readonly IHttpContextAccessor _contextAccessor;
public EditCustomerAuthorizationHandler(DbContext c, IHttpContextAccessor ca)
{
_context = c;
_contextAccessor = ca;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, EditCustomerRequirement requirement)
{
//here we have the access to DB and HttpContext
//we can get the information if the user is superuser from either db or httpContext
var isSuperUser = await IsSuperUser(_context);
//or
var isSuperUser = IsSuperUser(_contextAccessor);
if(isSuperUser)
{
context.Succeed(requirement);
}
else
{
//the current implementation of EditCustomer Policy
}
}
}
IsSuperUser is some kind of method that we could make to get the information from either db context or http context.
This question is essentially the same as the one here, but, for asp.net core while using the asp.net core cookie middleware.
Is accessing query string/request body data possible on validation, and if it is, would you encourage the idea? It seems that according to this that it is very much possible, however, are the same rules in play from big boy asp.net (such as you are only to read the request data once in a given requests lifetime)?
Example: I'm creating an app where people have one account, but, are members of different teams. They can perform many different actions in the app, and, they can perform that action while in the "context" of one team or another that they are a member of. So, I have a teamId integer being passed in requests made to the server. I'd like to pull claims off the ClaimsPrincipal verifying that they really are a member of that team in the authorization portion of the pipeline.
As you said it is possible to access request's data on OnValidatePrincipal event. So, you can write something like this:
OnValidatePrincipal = async (context) =>
{
if (context.Request.Path.Value.StartsWith("/teams/"))
{
var teamId = // get team id from Path;
if (user is not team member)
{
context.Response.StatusCode = 403;
}
}
}
However, i think your requirement is related Authorization rather than Authentication. I would use Policy-Based Authorization to handle the requirement. Example policy should be like this:
Requirement and Handler:
public class TeamMemberHandler: AuthorizationHandler<TeamMemberRequirement>
{
private readonly IActionContextAccessor _accessor; // for getting teamId from RouteData
public TeamMemberHandler(IActionContextAccessor accessor)
{
_accessor = accessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TeamMemberRequirement requirement)
{
var teamId = // get teamId with using _accessor
if (user is not member of team(by teamId))
{
context.Fail();
}
return Task.FromResult(0);
}
}
public class TeamMemberRequirement : IAuthorizationRequirement
{
}
Configure Services:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddAuthorization(options =>
{
options.AddPolicy("TeamMember",
policy => policy.Requirements.Add(new TeamMemberRequirement()));
});
services.AddSingleton<IAuthorizationHandler, TeamMemberHandler>();
}
Finally use it on top of controller(or if you want, you can add filter globally)
Authorize[(Policy = "TeamMember")]
public class TeamHomeController : Controller
{
// Authorize[(Policy = "AnotherPolicy")]
public IActionResult Index(){}
}