It turns out I was overthinking this after all.
Since I am dealing with an API backend, all I needed to do was to validate IDP Bearer tokens, not to create them. In the end, I was able to validate 3 ID providers using the folowing simple code:
services.AddAuthentication(OKTA_SCHEME)
.AddJwtBearer(ADFS_SCHEME, options =>
{
options.Authority = adfsConfig.authority;
options.Authority = adfsConfig.authority;
})
.AddJwtBearer(GOOGLE_SCHEME, jwt => jwt.UseGoogle(
clientId: googleConfig.clientId
))
.AddJwtBearer(OKTA_SCHEME, options =>
{
options.Authority = oktaConfig.authority;
options.Audience = oktaConfig.audience;
});
Note that this required the installation of one additional nuget package to simplify the validation of the Google tokens, which don't appear to follow the standard: Hellang.Authentication.JwtBearer.Google.
At this point I can authorize using attributes like:
[Authorize(AuthorizationSchemes = OKTA_SCHEME)]
...or set up policies based on the schemes.
The second part problem was to link my various logons to users in a local database, which I ended up doing using a custom IClaimsTransformation that uses the information populated to ClaimsPrincipal to lookup a the user in my database, and add an "Employee" role claim, if they are found.
public class EmployeeClaims : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (!principal.HasClaim(a => a.Type == "EmployeeNumber"))
{
Employee employee = lookupEmployee(principal);
if (employee != null)
{
ClaimsIdentity id = new ClaimsIdentity();
id.AddClaim(new Claim(ClaimTypes.Role, "Employee"));
id.AddClaim(new Claim("EmployeeNumber", employee.EmployeeNumber.ToString()));
principal.AddIdentity(id);
}
}
return Task.FromResult(principal);
}
private Employee lookupEmployee(ClaimsPrincipal principal) {
string issuer = principal.Claims.Single(a => a.Type == "iss").Value;
if (issuer.Contains("google.com"))
...
}
}
This IClaimsTransformation is then registered by:
services.AddScoped<IClaimsTransformation, EmployeeClaims>();
Now I can additionally authorize employees with:
[Authorize(Roles = "Employee")]