aspnet / Security

[Archived] Middleware for security and authorization of web apps. Project moved to https://github.com/aspnet/AspNetCore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AddJwtBearer with MetadataAddress does not use the correct Issuer value on ADFS 2016

rasitha1 opened this issue · comments

If I want to use ADFS 2016 OIDC with JWT tokens, is .AddJwtBearer() the right option? If so, there's an issue where the framework uses the value from "issuer" property (from /adfs/.well-known/openid-configuration) while JWT tokens issued by ADFS uses "access_token_issuer" as the "iss" value.

  • My app is .NET Core 2.1.1
  • This is only an issue when using MetadataAddress and let the framework discover "issuer". My workaround is to hard-code the ValidIssuer in validation parameters.
  • Looks like I could also change the ADFS Federation Identifier but that's not possible due to the impact to all existing apps.

Here's the exception:

Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException: IDX10205: Issuer validation failed. Issuer: 'http://adfs.myco.com/adfs/services/trust'. Did not match: validationParameters.ValidIssuer: 'null' or validationParameters.ValidIssuers: 'https://adfs.myco.com/adfs'. at Microsoft.IdentityModel.Tokens.Validators.ValidateIssuer(String issuer, 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()
Here's a snippet from ADFS adfs/.well-known/openid-configuration

`{
"issuer": "https://adfs.myco.com/adfs",

"id_token_signing_alg_values_supported": [
    "RS256"
],
"token_endpoint_auth_signing_alg_values_supported": [
    "RS256"
],
"access_token_issuer": "http://adfs.myco.com/adfs/services/trust",

`

Prior discussion: #673

If you can try the suggestions mentioned in #673, that would be great. If that doesn't work, we'll try to come up with a Plan B.

That didn't work...at least not with what I'm trying to do.

I'm trying to utilize the OIDC Discovery information so I don't have to manually retrieve/manage keys and any other metadata. Looks like if I initialize options.Configuration = new OpenIdConnectConfiguration{ Issuer = "http://adfs.myco.com/adfs/services/trust" }; then configuration is not retrieved from the discovery endpoint and I have to provide signing keys.

Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Failed to validate the token.

Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)

I can set options.TokenValidationPrameters.ValidIssuer="http://adfs.myco.com/adfs/services/trust" and that will work but not ideal that I have to manually set it.

It seems like using access_token_issuer is unique to ADFS in the OIDC Discovery document? I see it's documented here

I see references about this issue here as well.

In the OWIN world we used UseActiveDirectoryFederationServicesBearerAuthentication and FederationMetadata.xml endpoint was used to get Issuer so the values matched.

Maybe similar to AddAzureADBearer a dedicated AddAdfsBearer is needed?

Your best option in the short term might be to customize the options.ConfigurationManager in a way that lets you transform the metadata before it's passed to the JwtBearer handler.

Here's the base implementation you may be able to wrap:
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs

And this shows how we initialize it:

Thanks for the input. It seems like there's no clean way to do this. If I create a custom IConfigurationManager I have to duplicate the logic in JwtBearerPostConfigureOptions. And it seems like the best place to account for this difference in ADFS discovery info is in the IConfigurationRetriever per the current setup.

Does it make sense to allow overriding IConfigurationRetriever by having a property for that in JwtBearerOptions? i.e. change JwtBearerPostConfigureOptions.PostConfigure method from

options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(),
                        new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata });

To something like this:

options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, 
options.ConfigurationRetriever ?? new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata });

Then for ADFS JWT use case we can create a separate retriever that deserializes the discovery information into a subclass of OpenIdConnectConfiguration that has a property for "access_token_iusser" and use that for Issuer.

Another thing to note is that ADFS uses the correct Issuer value when calling the /authorize endpoint (for browser flows) and uses access_token_issuer when /oauth2/token endpoint is used. Don't know if that was done for some reason but I also wonder if that's the place that should get fixed instead.

@brentschmaltz / @jmprieur - any ideas on this and how to work with ADFS in this scenario?

@brentschmaltz thanks for the reply. I'm not clear on how I can use these in my case.

The issue is that ADFS is using two different Issuer values depend on which endpoint we use to get the token. (/authorize vs /oauth2/token) and that seems to be by design.

Both issuer values are available in the .well-known/openid-configuration page:

{
 "issuer": "https://adfs.myco.com/adfs",
 "access_token_issuer": "http://adfs.myco.com/adfs/services/trust"
}

What we need is a way to get both these values into ValidIssuers list. (Earlier I thought using access_token_issuer value for JWT setup but that will be an issue for SPAs)

Looking at OpenIdConnectConfiguration class, it looks like there's this AdditionalData which should have access_token_issuer after deserialization of OIDC metadata.

It's almost like there should be a way to do additional processing of metadata in GetConfigurationAsync after metadata is deserialized.

Maybe ConfigurationManager ctor can take in an additional parameter of some sort to handle this?

Then JwtBearerHandler will work as is. However this will still require a separate AddAdfsBearer() extension method I guess, to configure it.

Or, :) maybe there is a way to give additional Issuer property names for metadata processing so that value for access_token_issuer is automatically added to ValidIssuers if available in AdditionalData dictionary.

@brentschmaltz , anything else to add?

@rasitha1 @Eilon a solution is in "your" issuer validator, you recognize that the issuer == what you got from config + "/services/trust"

OR

you can make use of our 5.3.0 release (just dropped) where TokenValidationParameters has a property bag that you can set an issuer you trust OR the whole configuration object.

@brentschmaltz thanks for your feedback. My current workaround is your second suggestion. However I'm using the ValidIssuers array to add the /services/trust identifier so at runtime, both issuers are considered valid issuers. (i.e. the other one is pulled in via discovery page)

I'm worried about doing your first solution because I'd rather not have to worry about "how" to properly do issuer validation and rely on the framework implementation to get it done right.

I still believe ConfigurationManager should allow some sort of "post-processing" of configuration data. I don't see any way to utilize the AdditionalData values for further setting up my auth configuration unless I implement my own ConfigurationManager which seems unnecessary and risky.

Here are all the values in AdditionalData when using ADFS 2016 OIDC and I don't see a way to use them.

{
  "access_token_issuer": "http://myadfs.myco.com/adfs/services/trust",
  "microsoft_multi_refresh_token": true,
  "capabilities": [],
  "as_access_token_token_binding_supported": true,
  "as_refresh_token_token_binding_supported": true,
  "resource_access_token_token_binding_supported": true,
  "op_id_token_token_binding_supported": true,
  "rp_id_token_token_binding_supported": true
}

If you can provide some post-processing capabilities for _currentConfiguration it makes the ConfigurationManager class more flexible allowing handling of some of the extensions to OIDC.

I don't mean to drag this for too long, I just wanted to bring this up as a limitation in the current implementation that maybe you can consider making more flexible in a future release. For now I have the workaround I mentioned above.

Thanks all!

@rasitha1 I opened AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet#1030 as that's where a fix could show up. I have pinged some folks on the ADFS team to see what they were thinking.

@Eilon left the issue open for now.

Thanks @brentschmaltz , since there doesn't appear to be further action here, I'm closing the issue.

All - please refer to AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet#1030 to track the status of a potential fix.

@Eilon @brentschmaltz once (if?) OpenIdConnectConfiguration gets this new property are you planning on changing JwtBearerHandler to take this change in to account?

Today:

var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;

To something like:

var issuers = new List<string> { _configuration.Issuer };
if(_configuration.AccessTokenIssuer != null)
    issuers.Add(_configuration.AccessTokenIssuer);

validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;

Thanks!

@rasitha1 It sound possible, but let's revisit that question once OpenIdConnectConfiguration gets sorted out.

@rasitha1 @Tratcher We have to be careful when we would use this value. The user would have to 'opt in' otherwise we could introduce an issue when someone (although unlikely) overloaded that value. Particularly OIDC should not use that value.