I was trying to make a custom authorization attribute in ASP.NET vNext, until I found this excelent answer from #blowdart in this post:
https://stackoverflow.com/a/31465227/1756978
indicating that Authorization requirements is now the way to go. The answer is very clarifying but doesn't indicates how to pass a parameter to this requirements / policies.
What I'm trying to do is porting a MVC 5 custom authorization attribute which has this signature:
[Autorizacion(Requires = enumPermission.DeleteCustomer)]
since I use a very customised set of permissions mirrored in the backend/frontend as enums/strings.
As this features are still not documented I feel a little lost... Could anybody give guidance about?
Thanks in advance
I happen to comes up with a workround that can satisfy my requirement, hope it will help your too.
In my case, I need to pass IHttpContextAccessor and EFCore's AppDbContext to my Requirement class.
in my Startup.cs, I write something like this:
services.AddAuthorization(options =>
{
options.AddPolicy("ThePolicy", policy => policy.Requirements.Add( new ThePolicyRequirement() ));
});
services.AddScoped<IAuthorizationHandler, ThePolicyAuthorizationHandler>();
the ThePolicyAuthorizationHandler class:
public class ThePolicyAuthorizationHandler : AuthorizationHandler<ThePolicyRequirement>
{
readonly AppDbContext _appContext;
readonly IHttpContextAccessor _contextAccessor;
public ThePolicyAuthorizationHandler(AppDbContext c, IHttpContextAccessor ca)
{
_appContext = c;
_contextAccessor = ca;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ThePolicyRequirement requirement)
{
var result = await requirement.isPass(_appContext, _contextAccessor, context);
if (result)
context.Succeed(requirement);
else
context.Fail(requirement);
}
}
and ThePolicyRequirement class:
public class ThePolicyRequirement : IAuthorizationRequirement
{
AppDbContext _context;
IHttpContextAccessor _contextAccessor;
AuthorizationHandlerContext _authHandlerContext;
public async Task<bool> isPass(AppDbContext context, IHttpContextAccessor contextAccessor, AuthorizationHandlerContext authorizationHandlerContext)
{
_context = context;
_contextAccessor = contextAccessor;
_authHandlerContext = authorizationHandlerContext;
//logic here
return result;
}
}
The key idea is using ThePolicyAuthorizationHandler to obtain as much as possible all needed objects, and pass it to ThePolicyRequirementto do the logic of the authorization mechanism.
Indeed, #blowdart’s post is very insightful and from my understanding, the key thing to understand is the following:
Authorization act upon Identities. Identities are created by
authentication.
So it seems that identities are created by the authentication process.
Then (if you wish) you can make the authorization process kick in. This means creating a custom authorization requirements to which this requirement will be looking at those identities and act upon them.
In plain English, this is what I believe is happening:
As mentioned in blowdart’s post, we should have some sort of
authentication middleware that happens to do the actual
authentication. Once successfully authenticated, you take whatever
information you want from that now-authenticated user and create an
authenticated ClaimsPrincipal.
For example, we could store into that ClaimsPrincipal, the sets of
permission the user has.
Then, when you create your authorization requirement you look at the
ClaimsPrincipal, extract the sets of permissions from the
ClaimsPrincipal and take appropriate action based on whatever
business rules you want.
Assuming you can’t store the sets of permission into the
ClaimsPrincipal for some reason, one could easily store the UserId
and from within the requirement, read that UserId from the
ClaimsPrincipal, invoke the database and get the sets of permissions
and then act upon them.
Conclusion:
So in short, I don’t think you pass stuff to the requirement(s), I think you obtain them from within a ClaimsPrincipal.
In your example, you could create a requirement that reads the ClaimsPrincipal and compare whatever value with your Enum and act upon that.
Let us know what you’ve managed to do and if it works.
And if my understanding of this is wrong, then by all means, feel free to correct me since all of this is new stuff :-)
Related
Background
I've been following the documentation for using IdentityServer4 with single-page-applications on ASP.NET-Core 3.1 and as such created a project via the dotnet new react -au Individual command.
This creates a project which uses the Microsoft.AspNetCore.ApiAuthorization.IdentityServer NuGet package.
So far it's been really great and it got token-based authentication for my ReactJS application working without any pain!
From my ReactJS application, I can access the user information populated by the oidc-client npm package such as the username.
Also, calls to my Web APIs with the [Authorize] attribute work as expected: only calls with a valid JWT access token in the request header have access to the API.
Problem
I'm now trying to access basic user information (specifically username) from within a GraphQL mutation resolver via an injected IHttpContextAccessor but the only user information I can find are the following claims under IHttpContextAccessor.HttpContext.User:
nbf: 1600012246
exp: 1600015846
iss: https://localhost:44348
aud: MySite.HostAPI
client_id: MySite
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: (actual user GUID here)
auth_time: 1600012235
http://schemas.microsoft.com/identity/claims/identityprovider: local
scope: openid
scope: profile
scope: MySite.HostAPI
http://schemas.microsoft.com/claims/authnmethodsreferences: pwd
The same issue happens for Web API controllers as well.
Details
MySite is the namespace of my solution and is also what I have defined as a client in my appsettings.json file:
{
"IdentityServer": {
"Clients": {
"MySite": {
"Profile": "IdentityServerSPA"
}
}
}
}
My web application project's name is MySite.Host so MySite.HostAPI the name of the resource and scope that are automatically generated by calling AuthenticationBuilder.AddIdentityServerJwt().
... this method registers an <<ApplicationName>>API API resource with IdentityServer with a default scope of <<ApplicationName>>API and configures the JWT Bearer token middleware to validate tokens issued by IdentityServer for the app.
Research
According to a few answers on Stack Overflow, adding IdentityResources.Profile() resource via IIdentityServerBuilder.AddInMemoryIdentityResources() should do the trick but it looks like it's already available via the claims I posted above (scope: profile).
I nevertheless tried it but the result is that the authentication flow becomes broken: the redirect to the login page does not work.
All of the answers I've found also make a reference to a Config class like in this demo file which holds configurations that are mainly fed to IIdentityServerBuild.AddInMemory...() methods.
However, it seems that Microsoft.AspNetCore.ApiAuthorization.IdentityServer does most of this in its implementation and instead offers extendable builders to use.
From the IdentityServer documentation, I don't believe I need to add a Client because the access token already exists. The client ReactJS application uses the access_token from oidc-client to make authorised calls to my Web APIs.
It also doesn't appear like I need to add a Resource or Scope for the username information because I believe these already exist and are named profile. More to this point is that the documentation for "IdentityServerSPA" client profile states that:
The set of scopes includes the openid, profile, and every scope defined for the APIs in the app.
I also looked at implementing IProfileService because according to the documentation this is where additional claims are populated. The default implementation is currently being used to populate the claims that are being requested by the ProfileDataRequestContext.RequestedClaimTypes object and this mechanism already works because this is how the ReactJS client code receives them. This means that when I'm trying to get the user claims from ASP.NET-Core Identity, it's not properly populating ProfileDataRequestContext.RequestedClaimTypes or perhaps not even calling IProfileServices.GetProfileDataAsync at all.
Question
Considering that my project uses Microsoft.AspNetCore.ApiAuthorization.IdentityServer, how can I view the username from my ASP.NET-Core C# code, preferably with IHttpContextAccessor?
What you need to do is to extend the default claims requested by IdentityServer with your custom ones. Unfortunately, since you're using the minimalistic IdentityServer implementation by Microsoft, the correct way of making the client request the claims isn't easy to find. However, assuming you have only one application (as per the template), you could say that the client always wants some custom claims.
Very important first step:
Given your custom IProfileService called, say, CustomProfileService, after these lines:
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
you have to get rid of the implementation used in the scaffolded template, and use your own:
services.RemoveAll<IProfileService>();
services.AddScoped<IProfileService, CustomProfileService>();
Next, the actual implementation of the custom IProfileService isn't really hard if you start from Microsoft's version:
public class CustomProfileService : IdentityServer4.AspNetIdentity.ProfileService<ApplicationUser>
{
public CustomProfileService(UserManager<ApplicationUser> userManager,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory) : base(userManager, claimsFactory)
{
}
public CustomProfileService(UserManager<ApplicationUser> userManager,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
ILogger<ProfileService<ApplicationUser>> logger) : base(userManager, claimsFactory, logger)
{
}
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
string sub = context.Subject?.GetSubjectId();
if (sub == null)
{
throw new Exception("No sub claim present");
}
var user = await UserManager.FindByIdAsync(sub);
if (user == null)
{
Logger?.LogWarning("No user found matching subject Id: {0}", sub);
return;
}
var claimsPrincipal = await ClaimsFactory.CreateAsync(user);
if (claimsPrincipal == null)
{
throw new Exception("ClaimsFactory failed to create a principal");
}
context.AddRequestedClaims(claimsPrincipal.Claims);
}
}
With those two steps in place, you can start tweaking CustomProfileService's GetProfileDataAsync according to your needs. Notice that ASP.NET Core Identity by default already has the email and the username (you can see these in the claimsPrincipal variable) claims, so it's a matter of "requesting" them:
// ....
// also notice that the default client in the template does not request any claim type,
// so you could just override if you want
context.RequestedClaimTypes = context.RequestedClaimTypes.Union(new[] { "email" }).ToList();
context.AddRequestedClaims(claimsPrincipal.Claims);
And if you want to add custom data, for example, the users first and last name:
// ....
context.RequestedClaimTypes = context.RequestedClaimTypes.Union(new[] { "first_name", "last_name" }).ToList();
context.AddRequestedClaims(claimsPrincipal.Claims);
context.AddRequestedClaims(new[]
{
new Claim("first_name", user.FirstName),
new Claim("last_name", user.LastName),
});
User information can be retrieved via the scoped UserManager<ApplicationUser> service which is set up by the project template. The users's claims contains "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" (ClaimTypes.NameIdentifier) whose value is the user identifier. UserManager<>.FindByIdAsync() can then be used to retrieve the ApplicationUser associated with the user and which contains additional user information.
Note that this contacts the user store each time it's invoked. A better solution would be to have the extra user information in the claims.
First, explicitly add the IHttpContextAccessor service if you haven't already by calling services.AddHttpContextAccessor();
From within an arbitrary singleton service:
public class MyService
{
public MyService(
IHttpContextAccessor httpContextAccessor,
IServiceProvider serviceProvider
)
{
var nameIdentifier = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
using (var scope = serviceProvider.CreateScope())
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var user = await userManager.FindByIdAsync(nameIdentifier);
// Can access user.UserName.
}
}
}
UserManager<ApplicationUser> can be accessed directly within Razor pages and Controllers because these are already scoped.
In a Rest Api SaaS project developed with .Net Core 3.1.
When the user's subscription expires (needs to pay), what kind of a method would be better to follow.
There are 2 methods that I think of but I think there will be some problems in both of them.
Method 1) Checking the subscription status during JWT generate and not generating JWT if the subscription period has expired:
If I use this method;
Advantage: Since a token is not given to a user whose subscription expires,
they will not be able to access other endpoints.
I think this will work extremely safe without doing any other coding work.
Disadvantage: When I need to redirect the user to the payment page,
I will have to do a special work for the payment endpoints since there are no tokens.(Example: Password Reset Methods)
I will get it with query string, I think I can create a special token for this method.
But I think there might be a security bug because I couldn't protect this process with my standard authorization method?
Method 2) Even if the subscription expires, jwt will be generated, but membership will be restricted:
If I use this method;
Advantage: I can use my standard authorization method without any problems
when I need to direct the user to the payment endpoints or to another endpoints.
I will use with jwt and security bugs will be considerably reduced.
Disadvantage: I need to determine endpoints that cannot be accessed on the application for user whose subscription period expired
and I will need to code a working service in middleware that will make them inaccessible. (Like to permission methods)
This will both do extra coding work and each endpoint will require extra work.
These are my thoughts....
Or other solutions...
How should we restrict a user whose subscription expires and how should we act?
Thank you very much for your information sharing.
I solved the question I asked above using Method 2.
I wanted to explain how I did it, as I thought it might help those who investigate this question in the future.
I said in method 2, jwt has generated but membership restricted.
First of all, when generating tokens, I set claims whether they have a subscription or not.
....
new Claim(JwtClaimIdentifier.HasSubscription, hasSubscription)
I do not explain here in detail. Standard claims.
Subscription Control
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class SubscriptionRequiredAttribute : TypeFilterAttribute
{
public SubscriptionRequiredAttribute()
: base(typeof(SubscriptionFilter)) { }
}
--
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class AllowWithoutSubscriptionAttribute : Attribute
{
public AllowWithoutSubscriptionAttribute() { }
}
--
public class SubscriptionFilter : IAuthorizationFilter
{
private bool AllowWithoutSubscription(AuthorizationFilterContext context)
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
bool allowWithoutSubscriptionForMethod = controllerActionDescriptor.MethodInfo.CustomAttributes.Any(x => x.AttributeType == typeof(AllowWithoutSubscriptionAttribute));
if (allowWithoutSubscriptionForMethod)
return true;
bool allowWithoutSubscriptionForController = controllerActionDescriptor.ControllerTypeInfo.CustomAttributes.Any(x => x.AttributeType == typeof(AllowWithoutSubscriptionAttribute));
if (allowWithoutSubscriptionForController)
return true;
return false;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (AllowWithoutSubscription(context))
return;
var hasSubscription = context.HttpContext.User.Claims.First(x => x.Type == JwtClaimIdentifier.HasSubscription).Value.ToLower() == "true";
if (!hasSubscription)
context.Result = new BadRequestObjectResult(**ErrorCode**);
}
}
I added, an attribute that override subscription control.
For example; To use it in a controller or method that I need to override when checking subscriptions on base.
Use Controller
[SubscriptionRequired]
public class FooController
{
public async Task<IActionResult> FooMethodOne(){...}
public async Task<IActionResult> FooMethodTwo(){...}
[AllowWithoutSubscription]
public async Task<IActionResult> FooMethodThree(){...}
}
While FooMethodOne and FooMethodTwo above require subscription, FooMethodThree will work without subscription.
Likewise, all controls are called "AllowWithoutSubscription".
It can also be called "SubscriptionRequired" in methods.
Hopefully it benefits your business...
I want to improve my API's security with some sort of "self" policy to validate the call to some user actions (like DELETE user) is made by the same user the token was issued to. Is there a way to do this in a similar way to the policy based authorization?
I have a .Net Core 2.2 with MVC WebAPI running on Kestrel. I have users, roles and user-roles and I have token-based authentication with roles enabled. I can issue tokens and validate then with the "Authorize" attribute in the controllers. However, I've been looking for a way to validate that some actions to users are made only by the users itself, a "self" authentication policy to validate that, for example, user 3 is trying to delete user 3 and only user 3. I've dug up to the claims and everything and I know I can make a simple service passing the claims and the validating it but I wanted to do it in a smoother way similar to the policy-based or role-based authentication. I don't know if I can make it with some sort of middleware or something but it would be great to be able to make it as clean as possible.
[Edit]
The main purpose is to avoid users to delete resources created by other users and make them be able only to delete resources created by themselves.
[Edit2 - Solution]
Thanks to Paul Lorica's Answer I can now describe how I did it.
The first thing is to create a Requirement and a Handler similar to the examples provided by Microsoft in the docs. What we do is to add a Claim to the token generation method/service we have and add the ID as NameIdentifier. After that, we inject in the IHttpContextAccessor in the handler. And then we can validate if the ID in the request is the same than the Id in the Claim. So it was very easy.
I'm adding examples of logic to make it work.
PS: Inject IHttpContextAccessor as a singleton in the startup clas or it won't work.
Handler:
public class SelfUserHandler: AuthorizationHandler<SelfUserRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public SelfUserHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
SelfUserRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.NameIdentifier))
{
return Task.CompletedTask;
}
var nameIdentifier = context.User.FindFirst(c => c.Type == ClaimTypes.NameIdentifier).Value;
if (_httpContextAccessor.HttpContext.Request.Path.ToString().ToUpper().Contains(nameIdentifier.ToUpper()))
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
Requirement
public class SelfUserRequirement : IAuthorizationRequirement
{
public SelfUserRequirement() { }
}
Additional info:
Nate Barbettini Answer here
Joe Audette Answer here
First off, when your code validates against the policy, the policy has no understanding, and does not need to know, what you are doing.
I suppose you can retrieve the context via URL. So say if its a DELETE user/3
then you can create a policy that would check the user's claims that it has an ID == 3.
See the docs here on creating policies and accessing the httpContext
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.2
Its a bit of a naive check, I would rather just place that logic within the method of the controller.
I'm just getting started with ASP.NET Core Identity and have the following requirements defined:
public sealed class IsCustomerUserRequirement : IAuthorizationRequirement
public sealed class IsSuperUserRequirement : IAuthorizationRequirement
With the following basic handlers:
public class IsCustomerUserHandler : AuthorizationHandler<IsCustomerUserRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsCustomerUserRequirement requirement)
{
if (context.User.HasClaim(_ => _.Type == "customer"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class IsSuperUserHandler : AuthorizationHandler<IsSuperUserRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsSuperUserRequirement requirement)
{
if (context.User.IsInRole("super_user"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
I can then put these inside basic policies:
services
.AddAuthorization(options =>
{
options.AddPolicy("MustBeSuperUser", policy => policy.Requirements.Add(new IsSuperUserRequirement()));
options.AddPolicy("CustomersOnly", policy => policy.Requirements.Add(new IsCustomerUserRequirement()));
});
And apply it using [Authorize("CustomersOnly")], which works fine.
My requirement is to be able to allow super users, claim principals with the super_user role but without the customer claim, to also access Customers Only areas.
I have currently implemented this by changing the handler to manually check:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsCustomerUserRequirement requirement)
{
if (context.User.HasClaim(_ => _.Type == Claims.Customer) ||
context.User.IsInRole(Roles.SuperUser))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
My issue is this feels like I'm missing the point. Is there a better way to define this so I don't have to repeat the super user check in each handler in future?
The bigger picture in all this is I use IdentityServer4 (ASP.NET Identity-backed) to Authenticate, and then intend to use some JWT-based claims (one claim, two roles) to further identify the user Authorisation falls into an application-specific roles / permissions structure and some custom middleware that has nothing to do with Identity Server. What, if any, best practices are there around this topic?
“this feels like I'm missing the point” – Yes, in a way you are missing the point. You are doing role based authorization: A user can be a customer or a super user.
But instead, the new model is claims based authorization where the user has a claim about something, and you are using that to authorize them. So ideally, the super user would get the same claim the customer gets, and is allowed access to the resource that way. Such a claim also wouldn’t be called customer then, but be rather something that is a property of the user.
You can still use a role-based authorization model with claims but you should probably avoid mixing them. As you noticed yourself, this gets a bit weird eventually.
That being said, there are multiple ways to succeed a policy using different requirements. If you were using roles only (instead of that customer claim), you could simply use the built-in way:
options.AddPolicy("MustBeSuperUser", policy => policy.RequireRole("super_user"));
options.AddPolicy("CustomersOnly", policy => policy.RequireRole("customer", "super_user"));
That way, the CustomersOnly policy would be fulfilled by both customer and super_user roles.
Since you aren’t using a role for your customers, you will have to follow your requirements implementation here. The way authorization requirements work though is that you can have multiple handlers for the same requirement type and only one of them needs to succeed (as long as none fails) for the requirement to be successful.
So you could have your IsSuperUserHandler handle multiple requirements. YOu can follow the AuthorizationHandler<T> implementation to make this work:
public class IsSuperUserHandler : IAuthorizationHandler
{
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (var req in context.Requirements)
{
if (req is IsSuperUserRequirement || req is IsCustomerUserRequirement)
{
if (context.User.IsInRole("super_user"))
context.Succeed(req);
}
}
}
}
So your IsSuperUserHandler is now an authorization handler for both the IsSuperUserRequirement and the IsCustomerUserRequirement. So the CustomersOnly policy that requires the IsCustomerUserRequirement will also be fulfilled for super users.
I am trying to harness the authentication and authorisation features of servicestack so that I don't need to pollute my service code with this, which should lead to cleaner tests etc.
In my application, a user has permission to do something in a certain context.
ie A user can only view products that are in their product set.
To accomplish this I thought about decorating my productViewRequest dto with a permission attribute called canView and then to create my own implementation of IAuthSession to check that the user is requesting a product within their allowed set.
Would this be a decent approach or am I barking up the wrong tree?
Assuming it is a correct approach, how do I go about getting the context ie the productViewRequest object in the HasPermission call on my session implementation?
Thanks for your help
First I would check the ServiceStack built-in auth options https://docs.servicestack.net/authentication-and-authorization
If that doesn't fit your requirements, a request filter attribute will give you access to the request context.
public class CanViewAttribute : RequestFilterAttribute {
private readonly string permission;
public CanViewAttribute(string permission) {
this.permission = permission;
}
public override void Execute(IHttpRequest req, IHttpResponse res, object responseDto) {
// todo: check permission
if (!hasPermission) {
res.StatusCode = (int)HttpStatusCode.Forbidden;
res.EndRequest();
}
}
}