I'm trying to validate a Bearer jwt token in my backend using a filter (user retrieved from the jwt is not registered, I create a new one, and if it's registered then it's allowed to use the API). In order to achieve this I'm using Google OAuth2 service (and planing to use Facebook and Twitter as well for 'social login'), but I'm failing to validate the token.
In order to validate the token I first try to use Google's GoogleIdTokenVerifier (as explained here) but the tokens I used were reported to have an invalid signature (which I found odd as I got them using the google OAuth2 service), so I tried to validate them myself accessing the public signatures used by google and using Auth0 JWT library. Again I found myself with the previous issue (the signatures were not valid).
So 2 attempts and 2 failures, I thought the token was the issue and I used online google api service to validate it (https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=). It turns out that the token is not the issue so I went back again to zero.
Finally I decoded the token and compare the public keys that were used on the token (reported by jwt.io) with the one I was generating using google signatures, and found out that they do not match.
Token public key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkoT6VAUmOwqXQoyMQkk0
F1JJd81H7ksx7FHfKtqXvdrDt9LLr1IDJ+CFpbbn4SuJeYQcUo+lHA1/vbbtgCBS
yd/H86WGGARlPrsqFF+hbBBauhUQuXMxFxJKAiQS0WIbFvDkwRaNiGIMYQvzwDKb
ms5tCqZ04T5Qez9v64i3RlU8+upJxG9duzeXQjrnC5uJeeG+9fwE06RRZ1Y8Uul6
3Lpxicw5Alyd5HQIzF5vSSwjqdVrBUJAToxuTIqIHR24omrz1f7Jf97wy2U7KUoS
nytauyaZtph7RmsUPVMFr9fGMxNVU8tKEVeOYJv+piOc0gfPvIahbv2en0DhOax6
OQIDAQAB
-----END PUBLIC KEY-----
Generated public key (using google provided signatures)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqWaspOi9jHSmTMBq8EOP
XJDIkqXdSfb+VeEczodUetDIuA0/q5muW1YTcbmwzJwu9WxSi3ZOuRe1zjmP1HyT
z6ffnMFvwOrtN/pBahn++QYOIXlgw3SU4WtWn+VIRbuAVya+XxE0jfekwuuvtt3f
75WejmUbEWch6546YSu/57qM6IkOkJjxmu7rwcBuVruwPxL5IrGkZarEmv3wuYf5
trqZrjYXcd0O3/dzikFmUsel5DVR/y86i89lkl2ofL+netLNKC3edsMiKzXJExiC
0fu2lyLuf6IfM0MKcdwQS2m4JPmKtPh+s+wV9OtvWslHf+aVHCc+YRdhlARfN8o0
mwIDAQAB
-----END PUBLIC KEY-----
I really doubt that google was changed their OAuth2 signing public keys and forgot to update them to the public.
Is something I could be doing wrong?
Below I provide the code I use in order to retrieve the keys, and use them to validate the JWT token.
Public certs retrieval & public key generation:
final Mono<GoogleJWTDto> googleJWTDtoMono = webClient
.get()
.retrieve()
.bodyToMono(GoogleJWTDto.class);
googleJWTDtoMono.subscribe((googleJWTDto) -> {
for(final GoogleJWT googleJWT : googleJWTDto.keys) {
try {
final RSAKey rsaKey = new RSAKey.Builder(Base64URL.from(googleJWT.n), Base64URL.from(googleJWT.e))
.keyID(googleJWT.kid)
.keyUse(KeyUse.parse(googleJWT.use))
.build();
rsaKeys.put(googleJWT.kid, rsaKey);
} catch (ParseException e) {
LOGGER.error("the key (kid: {} from: {}) could not be loaded", googleJWT.kid, "google");
}
}
});
Validation key selection:
final DecodedJWT decodedJWT = JWT.decode(token);
final RSAKey rsaKey = getRSAKey(decodedJWT.getKeyId());
final boolean isValid = JWTUtils.verifyToken(decodedJWT, rsaKey);
JWTUtils class:
public class JWTUtils {
public static boolean verifyToken(final DecodedJWT decodedJWT, final RSAKey key) {
try {
final Algorithm algorithm = getTokenAlgorithm(decodedJWT, key);
final JWTVerifier verification = JWT.require(algorithm)
.withIssuer(decodedJWT.getIssuer())
.build();
verification.verify(decodedJWT);
return true;
} catch (JWTVerificationException | JOSEException e) {
return false;
}
}
private static Algorithm getTokenAlgorithm(final DecodedJWT decodedJWT, final RSAKey key) throws JOSEException {
switch (decodedJWT.getAlgorithm()) {
case "RS256":
return Algorithm.RSA256(key.toRSAPublicKey(), null);
default:
throw new IllegalArgumentException("the algorithm '" + decodedJWT.getAlgorithm() + "' is not supported");
}
}
}
PS: I know I can use the online service in order to validate the jwt, but since the whole purpose of jwt is the ability to validate them offline (with the public keys) I'd like to avoid this approach.