I followed Microsofts article to implement my own issuer validation ("Customizing token validation" is the headline of the section).
This seemed to work with JWT-Tokens issued in an app-only context, but failed when the first call to my API was a JWT token issued through user delegation.
I found that this line of code is causing the problem:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration);
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
// Your code to add extra configuration that will be executed after the current event implementation.
options.TokenValidationParameters.ValidIssuers = new[] { /* list of valid issuers */ };
options.TokenValidationParameters.ValidAudiences = new[] { /* list of valid audiences */};
}
});
This is the original code from the link I posted above. I implemented my own issuer validation in the following way:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.TokenValidtionParameters.RoleClaimType = "roles";
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
options.Authority = "https://login.microsoftonline.com/common";
var validTenants = FileTenantStore.Tenants.Select(x => x.AzureAdTenantId).ToList();
options.TokenValidationParameters.ValidIssuers = GetValidIssuers(validTenants);
options.TokenValidationParameters.IssuerValidator = ValidateIssuers;
};
});
I have a multi tenant app, so I need to let only some tenants pass and reject the most.
This solution behaves a little strange:
- Calling the API with an App-Only token works always.
- Calling the API with a Delegated token fails with the following error message and does not even jump into the callback:
Failed to validate the token. Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException: IDW10303: Issuer: 'https://login.microsoftonline.com/{OUR_TENANT_ID}/v2.0', does not match any of the valid issuers provided for this application. at Microsoft.Identity.Web.Resource.AadIssuerValidator.Validate(String actualIssuer, SecurityToken securityToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateIssuer(String issuer, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken) at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
So in this case "OnTokenValidated" is never called.
- Calling the API with an App-Only token on the first time, and then later with a delegated token works fine.
I could fix this problem with moving the lines in the "OnTokenValidated"-Callback one level above:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.TokenValidationParameters.RoleClaimType = "roles";
var validTenants = FileTenantStore.Tenants.Select(x => x.AzureAdTenantId).ToList();
options.TokenValidationParameters.ValidIssuers = GetValidIssuers(validTenants);
options.TokenValidationParameters.IssuerValidator = ValidateIssuers;
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
options.Authority = "https://login.microsoftonline.com/common";
};
});
I could even remove the callback "OnTokenValidated" now, but this does not feel right, because of the Microsoft article that is giving clear instructions.
Can I do it like this, or is my solution a security problem?
GetValidIssuers(validTenants)
? Based on the error message,'https://login.microsoftonline.com/{OUR_TENANT_ID}/v2.0'
, does not match any of the valid issuers returned byGetValidIssuers(validTenants)
. I guess that it returns the v1.0 version format:'https://sts.windows.net/{TENANT_ID}'
. – Allen Wuiss
claim)? docs.microsoft.com/azure/active-directory/develop/… – krishg