Search code examples
openidgoogle-oauthgoogle-apps

Intermittant Bad Jws Signature w/t Google Apps Open ID Authentication


We are using the Connect2D OAuth SDK (see: http://connect2id.com/products/nimbus-oauth-openid-connect-sdk) to authenticate Google Apps users via the Open ID protocol. Users report intermittent problems authenticating. Occasionally, our app fails to verify the token received from Google Apps.

OIDToken token = new OIDToken(getApplication(), tokenResponse.getIDToken(), id);
ReadOnlyJWTClaimsSet claimsSet = null;
try {
    claimsSet = token.verify();
} catch (Exception e) {
   throw new SecurityProviderException(994,"failed to verify token:"+e.getMessage());
}

The error reported is: "Bad JWS signature".

The problem is intermittent. Sometimes it works, sometimes it doesn't.

Much appreciate any advice.


Solution

  • I'm fairly sure this is the answer, but I might be wrong. We'll see.

    The main challenge with Google is that it seems to provide two alternate keys from its certificates endpoint. These have the same algorithm, but different key identifiers. It looks a bit like this:

    {
     "keys": [
      {
       "kty": "RSA",
       "alg": "RS256",
       "use": "sig",
       "kid": "5543f23f58e980646d8088d9393fcc3c1ac69ec5",
       "n": "xxx...",
       "e": "AQAB"
      },
      {
       "kty": "RSA",
       "alg": "RS256",
       "use": "sig",
       "kid": "da6625b36bc09d300353b28a741ce17525a4c33b",
       "n": "yyy...",
       "e": "AQAB"
      }
     ]
    }
    

    Unfortunately, the default implementation of the Connect2D JWT decoder selects a key by algorithm alone (i.e., just the RS256), so it is down to luck which one you get first. They seem to be added sequentially, so the timing pattern is (I am guessing) down to the order in which Google chooses a key being fairly consistent, combined with key rotation.

    i.e., it's logic is (simplified):

        JWSAlgorithm alg = signedJWT.getHeader().getAlgorithm();        
        JWSVerifier verifier = jwsVerifiers.get(alg);
        boolean verified = signedJWT.verify(verifier);
    

    This means the key id (kid) is ignored, even though it is present in the JWT header. In effect, the verifier selected is 50% likely to be the wrong one, and therefore obviously won't verify. All lookups are sequential, so probably stable, leading the observed behaviour of periods of consistent success or failure.

    A correct implementation of the JWT decoder should probably:

    • Use the kid as well as the algorithm to select which key to use
    • If no verifier is available for a given kid, should probably re-fetch the keys from the discovery API endpoint. This is because Google change their keys pretty often anyway, so if you initialize once you'll never get the new keys.

    So my inference is that this is correct but complicated OIDC behaviour from Google, which isn't by default implemented in the Connect2D OAuth SDK.

    I should probably report this on the Connect2D OAuth SDK bitbucket site.

    The "workaround" appears to be to not use DefaultJWTDecoder directly, but subclass it and add the missing logic, which as far as I can tell seems mostly doable, although the key updates might require a little thought.

    UPDATE

    Yes was the problem, confirmed by the comment at: https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/issues/138/google-openid-connect-cant-locate-which.

    The comment was:

    Sorry, there was a bit of a mix up, it is the Nimbus JOSE+JWT lib that now has a brand new framework for processing JWTs (http://connect2id.com/blog/nimbus-jose-jwt-4.0-rc1), however, its integration into the OIDC SDK is still on our to do list. We're currently busy completing a new release of the Connect2id server, and will not be able to dedicate time on the SDK until 29 July.

    If you're using v3.4.1 of the OIDC SDK you have two options:

    1. Implement an alternative JWTDecoder.
    2. Extend the JWSVerifier implementation to support key ID lookup.

    The first approach will probably be the easier of the two.

    Because I was working with Apache Shiro, I've had to integrate with that but the bulk of the logic is here, although I haven't tested it very heavily. The rest of that feature branch does seem to robustly authenticate against Google OIDC using ConnectID.

    The basic principle was to add the key rotation logic.

    If you can update to 4.0 of ConnectID you might get further faster, but I had to stick with 3.4.1 due to the various dependency layers.