How to verify JWT signature for Azure B2C ID Token in Java?

How to verify JWT signature for Azure B2C ID Token in Java? I successfully verified signatures with google open-id connection, but I was not able to verify signatures for Microsoft Azure B2C jwt id tokens. I used the B2C playground example here https://aadb2cplayground.azurewebsites.net/ . After registering and editing my profile, I grabbed this token.

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9..MPPkvUc0bHuVyf8hr4JZ0hG0mLE2pT7maDR-10e3XR8m6FtrsmQlkgvhnzfao94jPzDzX_CnG_Asfnqv04JeIpvQXBlViO63AlfZaZVllLByeJti5Uat1WepMPz5MRydk6b2o5w_xRfl7QOI-L9Yt8r7-rQX1FMuIPfvvsUity-M-H8s0XInvihxiKEHU_wvz6U017Tgjs4qcrpILM5Ziaxfb7oSxgECl3EDWAoITDy5B-rYCH_o-7mhxHQauUYgH5dUV2MrM8iuaMPoRc3r9Xk38SyfgS1-4taK_bi_AIutyOBX4O3cWbrvGDshQbHBW4BmjctTBT-xUPWboydpuA 

I pointed out java code to use the following endpoint to validate the token.

https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_sign_in

At the time this was written, it was json at this endpoint.

 { "keys": [ {"kid":"IdTokenSigningKeyContainer","use":"sig","kty":"RSA","e":"AQAB","n":"tLDZVZ2Eq_DFwNp24yeSq_Ha0MYbYOJs_WXIgVxQGabu5cZ9561OUtYWdB6xXXZLaZxFG02P5U2rC_CT1r0lPfC_KHYrviJ5Y_Ekif7iFV_1omLAiRksQziwA1i-hND32N5kxwEGNmZViVjWMBZ43wbIdWss4IMhrJy1WNQ07Fqp1Ee6o7QM1hTBve7bbkJkUAfjtC7mwIWqZdWoYIWBTZRXvhMgs_Aeb_pnDekosqDoWQ5aMklk3NvaaBBESqlRAJZUUf5WDFoJh7yRELOFF4lWJxtArTEiQPWVTX6PCs0klVPU6SRQqrtc4kKLCp1AC5EJqPYRGiEJpSz2nUhmAQ"} ] } , "use": "sig", "kty": "RSA", "e": "AQAB", "n": "tLDZVZ2Eq_DFwNp24yeSq_Ha0MYbYOJs_WXIgVxQGabu5cZ9561OUtYWdB6xXXZLaZxFG02P5U2rC_CT1r0lPfC_KHYrviJ5Y_Ekif7iFV_1omLAiRksQziwA1i-hND32N5kxwEGNmZViVjWMBZ43wbIdWss4IMhrJy1WNQ07Fqp1Ee6o7QM1hTBve7bbkJkUAfjtC7mwIWqZdWoYIWBTZRXvhMgs_Aeb_pnDekosqDoWQ5aMklk3NvaaBBESqlRAJZUUf5WDFoJh7yRELOFF4lWJxtArTEiQPWVTX6PCs0klVPU6SRQqrtc4kKLCp1AC5EJqPYRGiEJpSz2nUhmAQ"} { "keys": [ {"kid":"IdTokenSigningKeyContainer","use":"sig","kty":"RSA","e":"AQAB","n":"tLDZVZ2Eq_DFwNp24yeSq_Ha0MYbYOJs_WXIgVxQGabu5cZ9561OUtYWdB6xXXZLaZxFG02P5U2rC_CT1r0lPfC_KHYrviJ5Y_Ekif7iFV_1omLAiRksQziwA1i-hND32N5kxwEGNmZViVjWMBZ43wbIdWss4IMhrJy1WNQ07Fqp1Ee6o7QM1hTBve7bbkJkUAfjtC7mwIWqZdWoYIWBTZRXvhMgs_Aeb_pnDekosqDoWQ5aMklk3NvaaBBESqlRAJZUUf5WDFoJh7yRELOFF4lWJxtArTEiQPWVTX6PCs0klVPU6SRQqrtc4kKLCp1AC5EJqPYRGiEJpSz2nUhmAQ"} ] } 

Here is the java code I used

 package com.example import org.jose4j.jwk.HttpsJwks import org.jose4j.jwt.JwtClaims import org.jose4j.jwt.consumer.InvalidJwtException import org.jose4j.jwt.consumer.JwtConsumer import org.jose4j.jwt.consumer.JwtConsumerBuilder import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver class AzureB2CPOC7 { public static talk(){ String jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9..MPPkvUc0bHuVyf8hr4JZ0hG0mLE2pT7maDR-10e3XR8m6FtrsmQlkgvhnzfao94jPzDzX_CnG_Asfnqv04JeIpvQXBlViO63AlfZaZVllLByeJti5Uat1WepMPz5MRydk6b2o5w_xRfl7QOI-L9Yt8r7-rQX1FMuIPfvvsUity-M-H8s0XInvihxiKEHU_wvz6U017Tgjs4qcrpILM5Ziaxfb7oSxgECl3EDWAoITDy5B-rYCH_o-7mhxHQauUYgH5dUV2MrM8iuaMPoRc3r9Xk38SyfgS1-4taK_bi_AIutyOBX4O3cWbrvGDshQbHBW4BmjctTBT-xUPWboydpuA"; HttpsJwks httpsJkws = new HttpsJwks("https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_sign_in"); HttpsJwksVerificationKeyResolver httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJkws); JwtConsumer jwtConsumer = new JwtConsumerBuilder() .setRequireExpirationTime() // the JWT must have an expiration time .setAllowedClockSkewInSeconds(3600) // allow some leeway in validating time based claims to account for clock skew .setRequireSubject() // the JWT must have a subject claim .setExpectedIssuer("https://sts.windows.net/775527ff-9a37-4307-8b3d-cc311f58d925/") // whom the JWT needs to have been issued by .setExpectedAudience("bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4") // to whom the JWT is intended for .setVerificationKeyResolver(httpsJwksKeyResolver) .build(); try { // Validate the JWT and process it to the Claims JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt); System.out.println("JWT validation succeeded! " + jwtClaims); } catch (InvalidJwtException e) { // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway. // Hopefully with meaningful explanations(s) about what went wrong. System.out.println("Invalid JWT! " + e); } } } L9Yt8r7-rQX1FMuIPfvvsUity-M-H8s0XInvihxiKEHU_wvz6U017Tgjs4qcrpILM5Ziaxfb7oSxgECl3EDWAoITDy5B-rYCH_o-7mhxHQauUYgH5dUV2MrM8iuaMPoRc3r9Xk38SyfgS1-4taK_bi_AIutyOBX4O3cWbrvGDshQbHBW4BmjctTBT-xUPWboydpuA"; package com.example import org.jose4j.jwk.HttpsJwks import org.jose4j.jwt.JwtClaims import org.jose4j.jwt.consumer.InvalidJwtException import org.jose4j.jwt.consumer.JwtConsumer import org.jose4j.jwt.consumer.JwtConsumerBuilder import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver class AzureB2CPOC7 { public static talk(){ String jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9..MPPkvUc0bHuVyf8hr4JZ0hG0mLE2pT7maDR-10e3XR8m6FtrsmQlkgvhnzfao94jPzDzX_CnG_Asfnqv04JeIpvQXBlViO63AlfZaZVllLByeJti5Uat1WepMPz5MRydk6b2o5w_xRfl7QOI-L9Yt8r7-rQX1FMuIPfvvsUity-M-H8s0XInvihxiKEHU_wvz6U017Tgjs4qcrpILM5Ziaxfb7oSxgECl3EDWAoITDy5B-rYCH_o-7mhxHQauUYgH5dUV2MrM8iuaMPoRc3r9Xk38SyfgS1-4taK_bi_AIutyOBX4O3cWbrvGDshQbHBW4BmjctTBT-xUPWboydpuA"; HttpsJwks httpsJkws = new HttpsJwks("https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_sign_in"); HttpsJwksVerificationKeyResolver httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJkws); JwtConsumer jwtConsumer = new JwtConsumerBuilder() .setRequireExpirationTime() // the JWT must have an expiration time .setAllowedClockSkewInSeconds(3600) // allow some leeway in validating time based claims to account for clock skew .setRequireSubject() // the JWT must have a subject claim .setExpectedIssuer("https://sts.windows.net/775527ff-9a37-4307-8b3d-cc311f58d925/") // whom the JWT needs to have been issued by .setExpectedAudience("bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4") // to whom the JWT is intended for .setVerificationKeyResolver(httpsJwksKeyResolver) .build(); try { // Validate the JWT and process it to the Claims JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt); System.out.println("JWT validation succeeded! " + jwtClaims); } catch (InvalidJwtException e) { // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway. // Hopefully with meaningful explanations(s) about what went wrong. System.out.println("Invalid JWT! " + e); } } } 

First I got this error message "Could not find a suitable validation key for JWS w / header". So, I created a local web server and copied json from the microsoft json endpoint, but I replaced "kid": "IdTokenSigningKeyContainer" with "kid": "MnC_VZcATfM5pOYiJHMba9goEKY".

This change fixed the error message "Could not find a suitable confirmation for JWS w / header", but instead I received the following error: "JWS signature is not valid".

I am looking for a java solution to verify the signature of the jwt id above. Thanks in advance.

+7
source share
2 answers

Try using https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys for the HTTPS JWKS location. There is another key, and he has a child and x5t, which correspond to what is in JWT.

+3
source

This URL:

https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_sign_in

this is just an example of a common endpoint provided for each b2c tenant

https://login.microsoftonline.com/uallyyour client name} /discovery/v2.0/keys?p= {your login policy}

Azure b2c tokens do not provide the key itself, but only the key identifier "kid", however, the json object provided at the user endpoint contains all the necessary components to encode the verification key, you just need the values ​​"e" and "n"

 public class KeyUtilHandler { // values "n" and "e" from json object at // https://login.microsoftonline.com/{your tenant name}/discovery/v2.0/keys?p={your sign in sign up policy} private String eValue; private String nValue; public String stringPublicKey(){ byte[] modulusBytes = Base64.getUrlDecoder().decode(nValue); BigInteger modulusInt = new BigInteger(1, modulusBytes); byte[] exponentBytes = Base64.getUrlDecoder().decode(eValue); BigInteger exponentInt = new BigInteger(1, exponentBytes); KeyFactory keyFactory; RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(modulusInt, exponentInt); String encodedStringKey = null; { try { keyFactory = KeyFactory.getInstance("RSA"); RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(publicSpec); byte [] encodedKey = publicKey.getEncoded(); encodedStringKey = Base64.getEncoder().encodeToString(encodedKey); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { e.printStackTrace(); } } return String.format("-----BEGIN PUBLIC KEY-----%s-----END PUBLIC KEY-----" , encodedStringKey); } 
0
source

All Articles