'Overriding OnTokenValidated JwtBearerEvents with Custom function .NET Core 2
In my API project I am handling authentication with JwtBearer (users login using Azure). When the API is called the token is being validated with the defined Azure instance and this all works fine.
When a token is being validated successfully, the logged in user is being inserted in our own database with the proper roles. The way this is being handled now is as follow:
// Add authentication (Azure AD)
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Audience = this.Configuration["AzureAd:ClientId"];
options.Authority = $"{this.Configuration["AzureAd:Instance"]}{this.Configuration["AzureAd:TenantId"]}";
options.Events = new JwtBearerEvents()
{
OnTokenValidated = context =>
{
// Check if the user has an OID claim
if (!context.Principal.HasClaim(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier"))
{
context.Fail($"The claim 'oid' is not present in the token.");
}
ClaimsPrincipal userPrincipal = context.Principal;
// Check is user exists, if not then insert the user in our own database
CheckUser cu = new CheckUser(
context.HttpContext.RequestServices.GetRequiredService<DBContext>(),
context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>(),
userPrincipal);
cu.CreateUser();
return Task.CompletedTask;
},
};
});
This is working fine but it is not the most beautiful / proper way to do it. I would say I should use Dependency Injection / Overriding the OnTokenValidated event and integrate the 'CheckUser' logic there so the startup class stays uncluttered.
Sadly my knowledge about the DI is lacking and I am not entirely sure what the best way is to handle this properly. Therefore I looked a bit around and found a post which was exactly describing my problem:
Problems handling OnTokenValidated with a delegate assigned in startup.cs
After reading this post I tried to modify it a bit with my own logic, I ended up with the following:
In the Startup:
services.AddScoped<UserValidation>();
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Audience = this.Configuration["AzureAd:ClientId"];
options.Authority = $"{this.Configuration["AzureAd:Instance"]}{this.Configuration["AzureAd:TenantId"]}";
options.EventsType = typeof(UserValidation);
});
The Custom JwtBearerEvents class:
public class UserValidation : JwtBearerEvents
{
private string UserID { get; set; }
private string UserEmail { get; set; }
private string UserName { get; set; }
public override async Task TokenValidated(TokenValidatedContext context)
{
try
{
TRSContext context2 = context.HttpContext.RequestServices.GetRequiredService<TRSContext>();
UserManager<ApplicationUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
ClaimsPrincipal userPrincipal = context.Principal;
this.UserID = userPrincipal.Claims.First(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
if (userPrincipal.HasClaim(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"))
{
this.UserEmail = userPrincipal.Claims.First(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value;
}
if (userPrincipal.HasClaim(c => c.Type == "name"))
{
this.UserName = userPrincipal.Claims.First(c => c.Type == "name").Value;
}
var checkUser = userManager.FindByIdAsync(this.UserID).Result;
if (checkUser == null)
{
checkUser = new ApplicationUser
{
Id = this.UserID,
Email = this.UserEmail,
UserName = this.UserEmail,
};
var result = userManager.CreateAsync(checkUser).Result;
// Assign Roles
if (result.Succeeded)
{
return;
}
else
{
throw new Exception(result.Errors.First().Description);
}
}
}
catch (Exception)
{
throw;
}
}
}
This is however not working for some reason. There is no error and UserValidation is never being hit (tried to set a debug point but it never hits) and it doesn't insert new users (it does when using the old code).
Anyone knows what I am doing wrong here or perhaps has some better ideas how to handle this?
Solution 1:[1]
I would suggest that you do basic token validation (things like Authority and Audience) in the startup as you have shown. I would suggest you use policy-based validation for specific claim validation. See Policy-based authorization in ASP.NET Core
The result will be code that is simpler, and easier to maintain.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | GlennSills |
