Closed mbjarland closed 4 years ago
As noted in issue #89, the ID token returned by azure ad contains the following type of header:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "aPctw_odvROoENg3VoOlIh2tiEs",
"kid": "aPctw_odvROoENg3VoOlIh2tiEs"
}
where the kid
specifically identifies the key that was used to to sign this token. We can find this same key id in the keys published by microsoft in the above analysis. I am no expert in jwt, jwk etc, but seems to me that the micronaut code should be using the indicated key directly instead of looping through and looking for one which supports the algorithm (which in this case does not exist).
@mbjarland since you have done all this analysis to identify the issue, fancy sending a PR to resolve the problem?
Seems like issue https://github.com/micronaut-projects/micronaut-security/issues/50 is also related.
@graemerocher I have spent some significant time mulling on this. A few big take aways:
if(config.supports(algorithm))
clause will solve this as the contained if (config.verify(signedJWT))
call with match against the kid
key id anyway which is exactly what the spec tells us to do. I will create a PR for this shortly. <<<< Quoting the openid spec
3.1.3.7. ID Token Validation
Clients MUST validate the ID Token in the Token Response in the following manner:
iss
(issuer) Claim.aud
(audience) Claim contains its client_id
value registered at the Issuer identified by the iss
(issuer) Claim as an audience. The aud
(audience) Claim MAY contain an array with more than one element. The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client.azp
Claim is present.azp
(authorized party) Claim is present, the Client SHOULD verify that its client_id
is the Claim Value.alg
value SHOULD be the default of RS256
or the algorithm sent by the Client in the id_token_signed_response_alg
parameter during Registration.HS256
, HS384
, or HS512
, the octets of the UTF-8 representation of the client_secret
corresponding to the client_id contained
in the aud
(audience) Claim are used as the key to validate the signature. For MAC based algorithms, the behavior is unspecified if the aud
is multi-valued or if an azp
value is present that is different than the aud
value.iat
Claim can be used to reject tokens that were issued too far away from the current time, limiting the amount of time that nonces need to be stored to prevent attacks. The acceptable range is Client specific.acr
Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate. The meaning and processing of acr Claim Values is out of scope for this specification.auth_time
Claim was requested, either through a specific request for this Claim or by using the max_age parameter
, the Client SHOULD check the auth_time
Claim value and request re-authentication if it determines too much time has elapsed since the last End-User authentication.end of openid spec quote >>>>
If I read the code correctly, and this is a tad complex so I might be missing something, micronaut is currently not doing a lot of the above. It might be worth having somebody take a somewhat longer look at the id token validation process to make sure we adhere to the spec.
@mbjarland thank you very much for your contribution. We truly appreciate your detailed analysis plus the accompanying PR. I have just merged it. Keep going!
Edit: Just noticed that this issue seems to be duplicating issue #89, leaving it here anyway as there is a fair bit of information in this issue
OpenID error for Azure AD: JWT signature validation failed for provider'
When trying to use openid connect against azure active directory, the token signature validation always fails with the error:
my application.yaml configuration is as follows:
For high level context, this is how far we get in the process:
micronaut successfully connecrts to the openid config which in my case is at:
https://login.microsoftonline.com/8056f686-b27e-4792-950e-b2770b2a9a2f/.well-known/openid-configuration
alg
key and micronaut incorrectly assumes that this key is always there, the signature validation will always fail.I.e. using micronaut openid against azure active directory will never work with the provided micronaut implementation.
Analysis and Comparison of OpenID providers
Microsoft Azure
To understand the issues, we need to look at the openid configurations and the jwk keys of azure as compared to the specification and some other relevant openid providers.
The azure openid config (which in my case is at https://login.microsoftonline.com/8056f686-b27e-4792-950e-b2770b2a9a2f/.well-known/openid-configuration) looks as follows:
this in turn gives us the jwks_uri
https://login.microsoftonline.com/common/discovery/keys
which contains the microsoft signing keys:For reference I will paste in a couple of relevant alternative openid provider configuartions before continuing with the analysis of why azure does not work.
Google Cloud
The same process using google; openid config:
which gives us the jwks_uri
https://www.googleapis.com/oauth2/v3/certs
with the following data::Amazon AWS
I didn't have the time nor inclination to setup an openid config with aws, but the jwks_uri for aws is
https://cognito-identity.amazonaws.com/.well-known/jwks_uri
and the relevant keys data looks as follows:Micronaut Code
Digging into the micronaut internals, we notice that the problem occurs in the following code:
in class
DefaultOpenIdTokenResponseValidator
. The call toparseJwtIfValidSignature
will eventually call the following code in JwrTokenValidationUtils:where the
config.supports(algorithm)
call will call the following code in classJwksSignature
:here we get to the crux of the problem. In the azure case, we have three json web keys so the the
getJsonWebKeys
call will return a list of three items. None of them however have a value for the algorithm (missingalg
key in the keys data above) property which means that thealgorithm::equals
call will always return false (since it is essentiallyalgorithm.equals(null)
in each case).I.e. the
config.supports(...)
call earlier will always return false, thus failing the jwt signature validation and resulting in the logged error.Further, if debug logging is enabled, the else clause in
JwrTokenValidationUtils
will execute the following line:where the
JwksSignature.supportedAlgorithmsMessage()
looks as follows:which in the azure ad case will throw an NPE...again, because the getAlgorithm method of the JWK class will return null. So we have a logged error and if you try to get more information by turning on trace logging, you get an NPE instead.
Reproducing Using Standalone Groovy Script
The following groovy script downloads the relevant micronaut dependency and emulates the situation when connecting to the three above mentioned providers, azure, google, and aws:
executing this script results in the following output:
What the Specification Says
As defined by RFC7517 for "JSON Web Key (JWK)", the
alg
key is optional:Note the ending: