Sorry for my english.
I have three projects: IdentityServer, Ensino.Mvc, Ensino.Api. The IdentityServer Project provides the main identity information and claims - claim Profile, claim Address, claim Sid... etc, from the IdentityServer4 library. The Ensino.Mvc Project gets this information in a token and sends it to the API, so that the MVC is grranted access to the resources. The token contains all the claims provided by IdentityServer. But in the API, I need to generate other claims that are API specific, like: claim EnrollmentId that corresponds to claim Sid from the token. And also I want to add this claim in HttpContext for future purposes. Can somebody tell me how to achieve this?
I have this code in Startup.ConfigureServices:
// Add identity services
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ApiName = "beehouse.scope.ensino-api";
});
// Add mvc services
services.AddMvc();
In other Project, without API, just mvc, I have inherited UserClaimsPrincipalFactory
and overridden CreateAsync
to add additional claims. I like to do something like this but in the API project. Is it possible?
What the best approach to do this?
EDIT: After some research, what I want to do is: Authentication by IdentityServer and set authorization in api, based on claims and specific api database data.
In your API project you can add your own event handler to options.JwtBearerEvents.OnTokenValidated
. This is the point where the ClaimsPrincipal
has been set and you can add claims to the identity or add a new identity to the principal.
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ApiName = "beehouse.scope.ensino-api";
options.JwtBearerEvents.OnTokenValidated = async (context) =>
{
var identity = context.Principal.Identity as ClaimsIdentity;
// load user specific data from database
...
// add claims to the identity
identity.AddClaim(new Claim("Type", "Value"));
};
});
Note that this will run on every request to the API so it's best to cache the claims if you're loading info from database.
Also, Identity Server should only be responsible for identifying users, not what they do. What they do is application specific (roles, permissions etc.) so you're correct in recognising this and avoiding the logic crossover with Identity Server.
Making your own AuthenticationHandler
that uses the IdentityServerAuthenticationHandler
would be the best option. This would allow you to use DI, reject authentication, and skip the custom authentication handler when it is not needed.
Example AuthenticationHandler
that first authenticates the token and then adds more claims:
public class MyApiAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Pass authentication to IdentityServerAuthenticationHandler
var authenticateResult = await Context.AuthenticateAsync("Bearer");
// If token authentication fails, return immediately
if (!authenticateResult.Succeeded)
{
return authenticateResult;
}
// Get user ID from token
var userId = authenticateResult.Principal.Claims
.FirstOrDefault(c => c.Type == JwtClaimTypes.Subject)?.Value;
// Do additional checks for authentication
// e.g. lookup user ID in database
if (userId == null)
{
return AuthenticateResult.NoResult();
}
// Add additional claims
var identity = authenticateResult.Principal.Identity as ClaimsIdentity;
identity.AddClaim(new Claim("MyClaim", "MyValue"));
return authenticateResult;
}
}
Add handler to services:
services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
// ...
})
.AddScheme<AuthenticationSchemeOptions, MyApiAuthenticationHandler>("MyApiScheme", null);
Now you can use either scheme:
// Authenticate token and get extra API claims
[Authorize(AuthenticationSchemes = "MyApiScheme")]
// Authenticate just the token
[Authorize(AuthenticationSchemes = "Bearer")]
Note that IdentityServerAuthenticationHandler
does the same thing, using the dotnet JWT handler:
public class IdentityServerAuthenticationHandler : AuthenticationHandler<IdentityServerAuthenticationOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
...
return await Context.AuthenticateAsync(jwtScheme);
...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With