Reputation: 5003
I am in the process of updating my application to use Azure Active Directory as an OAuth 2.0 Authentication Server. At the moment I am successfully using the Authorization Code grant type and receiving access_token and id_token values.
Now, I am trying to validate the returned id_token. I am following the steps outlined in the doco, and I am able to find the public key that was used to sign the JWT. For example, this is the record being returned by the Azure REST endpoint
As I understand it, these are the available public keys. I can filter it down to one (using the kid value in the returned JWT header). But what I'm struggling to understand is what each field is meant to represent.
Am I using the n/e fields to create the modulus and exponent values for the SHA256 Public Key? Am I meant to use the x5c value instead? Sorry if this is an obvious question, but is there any documentation on, given values like above, how a Public Key can be created for it? I am doing this in Java, so anything specific to that would be greatly appreciated.
Upvotes: 5
Views: 3728
Reputation: 1113
Here is a similar question I posted and got help on that might help. I think you want to use the x5c, which is the full certificate chain, and take the public key from that to validate the signature of your JWT. The modulus and exponent (n and e) will produce the public key of only the first certificate in the certificate chain, but that is insufficient to validate the signature.
Here are some code snippets I used to extract the public key from the endpoint. The AzurePublicKey class is just the POJO for the json.
private PublicKey convertKey(AzurePublicKey azKey) {
PublicKey publicKey = null;
BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(azKey.getN()));
BigInteger exponent = new BigInteger(1, Base64.getUrlDecoder().decode(azKey.getE()));
try {
publicKey = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
// load cert
CertificateFactory factory;
X509Certificate cert = null;
try {
factory = CertificateFactory.getInstance("X.509");
cert = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(azKey.getX5c().iterator().next())));
} catch (CertificateException e) {
e.printStackTrace();
}
// grab public key
publicKey = (RSAPublicKey)cert.getPublicKey();
System.out.println("[");
System.out.println("kid : " + azKey.getKeyIdentifier());
System.out.println("e : " + azKey.getE());
System.out.println("n : " + azKey.getN());
System.out.println("Maybe this : " + DatatypeConverter.printBase64Binary(publicKey.getEncoded()) );
System.out.println("]");
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return publicKey;
}
And the code to use that public key to validate the signature using the JJWT library.
Jws<Claims> claims = Jwts.parser()
.setSigningKeyResolver(signingKeyResolver)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""));
String issuer = claims.getBody().getIssuer();
if(issuer == null || !VALID_ISSUERS.contains(issuer))
throw new IncorrectClaimException(claims.getHeader(), claims.getBody(), "Invalid Issuer in Token.", new Throwable("Invalid Issuer in Token."));
The SigningKeyResolverImpl just provides the correct public key based on the kid
of the token header. If parsing does not throw an exception you can validate the token claims against values you were expecting as I've validated the issuer here against a list of VALID_ISSUERS
that I'm expecting.
It's been a while so I'm fuzzy but I hope this helps.
Upvotes: 2
Reputation: 14649
To verify the signature of id_token, we can use JwtSecurityTokenHandler
class if you were developing with C#. And you can refer JsonWebTokenValidator.cs for the code sample to using this class. I also copy this class here for convenience:
public class JsonWebTokenValidator
{
public JwtSecurityToken Validate(string token)
{
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningTokens = config.SigningTokens,
ValidateLifetime = false
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}
}
Upvotes: 1