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
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 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();
I love the way asp.net identity lets you neatly manage role based authorizations by just adding an annotation at the top of your controller method. But what if you are not using entity framework? What if you are using ADO.NET with ASP.NET Core API? How do you manage Role Based Authorization?
[Authorize(Roles = Role.Admin)]
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
Perhaps we could mimic this by creating ActionFilter and checking what is the value of global variable that holds user's role? What are my options?
This is a great case for policy-based authorization. You'll use a custom IAuthorizationHandler and IAuthorizationRequirement. See the documentation here: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-3.0
I've done this in a project recently. You create named policies, in your case something like "Admin". Your AuthorizationHandler will access the current user identity and can look up their current roles by injecting your services into the handler. Then you decorate your controller / actions with [Authorize(Policy = "Admin")].
In my project, my AuthorizationHandler and AuthorizationRequirement look like this:
public class CreateClientHandler : AuthorizationHandler<CreateClientRequirement, CreateClientRequest>, IAuthorizationRequirement
{
private readonly IStudioService _studioService;
public CreateClientHandler(
IStudioService studioService
)
{
_studioService = studioService;
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
CreateClientRequirement requirement,
CreateClientRequest resource
)
{
var userIdClaim = context.User.Claims.SingleOrDefault(c => c.Type == ClaimTypes.Name);
if (userIdClaim == null)
{
context.Fail();
return Task.CompletedTask;
}
if (resource != null)
{
var userId = int.Parse(userIdClaim.Value);
if (!UserIsAdmin(userId))
{
context.Fail();
return Task.CompletedTask;
}
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class CreateClientRequirement : IAuthorizationRequirement {}
In my ConfigureServices, in Startup.cs:
services.AddAuthorization(options =>
{
options.AddPolicy("CreateClient", policy =>
policy.Requirements.Add(new CreateClientRequirement()));
});
Once everything is configured, I can decorate controller actions with [Authorize(Policy = "CreateClient")].
I'm trying to add a new policy based authorization for a certain user who needs to execute certain actions of a controller that requires rights. I'm new with Policy based authorization but I followed all the instructions on this post, and seems pretty simple.
In my Startup.cs and after the AddMvc() method I have:
services.AddAuthorization(options =>
{
options.AddPolicy("AgentsActivityReport ", policy => policy.RequireUserName("AnaR"));
});
Then, In my controller action I have:
[Authorize(Policy = "AgentsActivityReport")]
public ActionResult AgentsActivity()
{
//some code
}
However, when I launch the application, I receive the following error:
InvalidOperationException: The AuthorizationPolicy named:
'AgentsActivityReport' was not found.
I have also readed a few other threads/posts such as:
Claim Based And Policy-Based Authorization With ASP.NET Core 2.1
reported issue
And everything seems pretty much correct. Any thougts?
Based on this post, I was able to declare and allow my user to invoke an action of a controller by using a requirement. Since my condition has to be, "allow certain roles OR a certain user named AnaR", I had to put that logic into the AuthorizationHandler.cs
Startup.cs
services.AddAuthorization(options => {
options.AddPolicy("ReportActivityPolicy", policy =>
{
policy.AddRequirements(new UserNameRequirement("AnaR"));
});
});
services.AddSingleton<IAuthorizationHandler, AgentsActivityAuthorizationHandler>();
And then, in a separate file:
public class AgentsActivityAuthorizationHandler : AuthorizationHandler<UserNameRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserNameRequirement requirement)
{
if (context.User.IsInRole("Administrator") || context.User.IsInRole("Manager") || context.User.Identity.Name == requirement.UserName)
{
context.Succeed(requirement);
}
return Task.FromResult(0);
}
}
public class UserNameRequirement : IAuthorizationRequirement
{
public UserNameRequirement(string username)
{
this.UserName = username;
}
public string UserName { get; set; }
}
Then, in my controller, the following:
[Authorize(Policy = "ReportActivityPolicy")]
public ActionResult AgentsActivity()
{
//code of your controller.
}
Hope it helps!
It seems that you register a policy named "AgentsActivityReport " with a white space at the end in the middleware but annotate the controller without the whitespace "AgentsActivityReport".
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(){}
}