Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I am using IdentityServer4.
I want to add other custom claims to access token but I'm unable to do this. I have modified Quickstart5 and added ASP.NET Identity Core and the custom claims via ProfileService as suggested by Coemgen below.
You can download my code here: [zip package][3]. (It is based on: Quickstart5 with ASP.NET Identity Core and added claims via ProfileService).
Issue: GetProfileDataAsync does not executed.
You should implement your own ProfileService.
Have a look in this post which I followed when I implemented the same:
https://damienbod.com/2016/11/18/extending-identity-in-identityserver4-to-manage-users-in-asp-net-core/
Here is an example of my own implementation:
public class ProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//>Processing
var user = await _userManager.GetUserAsync(context.Subject);
var claims = new List<Claim>
{
new Claim("FullName", user.FullName),
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
//>Processing
var user = await _userManager.GetUserAsync(context.Subject);
context.IsActive = (user != null) && user.IsActive;
}
}
Don't forget to configure the service in your Startup.cs (via this answer)
services.AddIdentityServer()
.AddProfileService<ProfileService>();
Ok the issue here is this:
although you have configured your available Identity resources correctly (both standard & custom), you also need to explicitly define which ones are a necessity when calling your api resource. In order to define this you must go to your Config.cs class on ExampleIdentityServer project and provide a third argument like on the new ApiResouirce constructor. Only those will be included into the access_token
// scopes define the API resources in your system
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API", new[] { JwtClaimTypes.Subject, JwtClaimTypes.Email, JwtClaimTypes.Phone, etc... })
};
}
In essence this means that I got my identity claims configured for my organization but there may be more than one APIs involved and not all of the APIs make use of all available profile claims. This also means that these will be present inside your ClaimsPrincipal all the rest can still be accessed through the "userinfo" endpoint as a normal http call.
NOTE: regarding refresh tokens:
If you chose to enable refresh tokens via AllowOfflineAccess = true, you may experience the same behavior upon refreshing the access_token "GetProfileDataAsync does not executed!". So the claims inside the access_token stay the same although you get a new access_token with updated lifetime. If that is the case you can force them to always refresh from the Profile service by setting UpdateAccessTokenClaimsOnRefresh=true on the client configuration.
Issue found.
In startup.cs, instead of adding services.AddTransient<IProfileService, ProfileService>();, add .AddProfileService<ProfileService>() to services.AddIdentityServer().
You will end up with
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<ProfileService>();
Thanks for Coemgen for helping out! Nothing wrong with the code, just the startup was wrong.
You can include any claim by using UserClaims option in your GetIdentityResources() in the config class :
UserClaims:
List of associated user claim types that should be included in the identity token. (As per the official documentation) http://docs.identityserver.io/en/release/reference/identity_resource.html#refidentityresource
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.
I have a multi-tenant .NET Core web app where the current user's tenant is resolved via middleware. In particular, tenants are resolved with a library called SaasKit.Multitenancy.
To use this library, you put this line in ConfigureServices():
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// (omitted for brevity)
// The 'Tenant' type is what you resolve to, using 'ApplicationTenantResolver'
services.AddMultitenancy<Tenant, ApplicationTenantResolver>();
// ...
}
And you put this line in Configure() to add it to the middleware pipeline:
public void Configure(IApplicationBuilder app)
{
// ...
app.UseAuthentication();
app.UseMultitenancy<Tenant>(); //this line
app.UseMvc(ConfigureRoutes);
// ...
}
This causes the following method in the middleware to be executed, which resolves the current user's tenant:
public async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
//whatever you need to do to figure out the tenant goes here.
}
This allows the result of this method (whichever tenant is resolved) to be injected into any class you want, like so:
private readonly Tenant _tenant;
public HomeController(Tenant tenant)
{
_tenant = tenant;
}
Up until now, we have been authenticating users with the .NET Identity platform, storing user data in our app's database. However, a new tenant of ours wants to be able to authenticate their users via SSO.
I have figured out most of the SSO stuff--I am using Azure AD to sign in users, and my organization's Azure AD tenant will be able to federate with their Identity Provider. In short, this code in ConfigureServices adds the Identity and AzureAD authentication:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// rest of the code is omitted for brevity
services.AddIdentity<ApplicationUser, ApplicationRole>(config =>
{
config.User.RequireUniqueEmail = true;
config.Password.RequiredLength = 12;
}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => _configuration.Bind("AzureAd", options)).AddCookie();
// policy gets user past [Authorize] if they are signed in with Identity OR Azure AD
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder(
AzureADDefaults.AuthenticationScheme,
IdentityConstants.ApplicationScheme
).RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
}
When using Identity, I have been able to resolve the users's tenant with the UserManager, like so:
public async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
TenantContext<Tenant> tenantContext = new TenantContext<Tenant>(new ApplicationTenant());
if (context.User.Identity.IsAuthenticated)
{
var user = await _userManager.Users
.Include(x => x.Tenant)
.FirstOrDefaultAsync(x => x.UserName == email);
if (user?.Tenant != null)
{
tenantContext = new TenantContext<Tenant>(user.Tenant);
_logger.LogDebug("The current tenant is " + user.Tenant.Name);
return await Task.FromResult(tenantContext);
}
}
return await Task.FromResult(tenantContext);
}
My plan was to modify this code so grabbed the current User's claims, which can be used to infer which tenant the user belongs to. However, when authenticating a user via Azure AD, HttpContext.User is always empty in the middleware, despite the user being signed in. It's not null, but HttpContext.User.Identity.IsAuthenticated is always false and HttpContext.User.Claims is empty. I only see the value of HttpContext.User populated once routing is complete and the code has reached a Controller.
I have tried reorganizing the middleware in pretty much every feasible way to no avail. What's confusing to me is that HttpContext.User is populated in the tenant resolver when the user is authenticated with Identity. With this in mind, I'm not sure how I can access the user's claims in the middleware when authenticating via Azure AD.
The best solution I can think of is to modify every instance the current tenant is injected into the code with a call to a method that resolves the tenant via claims. If the tenant is null in an area restricted with the [Authorize] attribute, it would imply the user is signed in via Azure AD, which would allow me to look at their claims. However, it really bothers me that I can't access the user's claims in the middleware, as I'm not sure what's really going on here.
Since you are trying to access HttpContext from a custom component you are going to want to add HttpContextAccessor to your service collection as follows:
services.AddHttpContextAccessor();
You can now resolve HttpContext as needed using dependency injection. This may or may not be helpful depending on how much control you have of the middleware that you are using.
For what it's worth, I've had little issue authenticating against AAD just using MSAL without additional third-party middleware. Good luck!
I suspect you might be running on .NET Core 2.0 and running into this issue.
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.
Visual Studio provides a nice ready template for ASP.NET WEB API project. There we have a set of account management functions dealing with ASP.NET Identity. However, one most fundamental function is missing both from automatically generated controllers and documentation. Namely, "~/Token" URL, which is used to grant WEB API access tokens is not mentioned anywhere.
I would like to write a custom controller to intercept all "~/Token" calls to make some logging and additional processing in a way similar to other WEB API controllers. How can I do it in a simple and natural way?
It seems you need OWIN OAuth 2.0 Authorization Server. This is the Microsoft extension to add the required functionality. It creates an oauth endpoint (e.g. /token) that you can use to get a token. You don't have a controller directly, but there is a special OWIN class connected to it that you will need to extend to add whatever you need.
You can find more details here and here.
It's a bit long reading, but it works and I have used it in a few projects.
Here is a simple example how you can do it (GrantResourceOwnerCredentials is the most important method for you):
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Add CORS e.g.
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
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 :-)