google-authenticationgoogle-oauthgoogle-openidgoogle-openidconnect

Can I get a consistent 'iss' value for a Google OpenIDConnect id_token?


I'm using Google's OpenIDConnect authentication, and I want to validate the JWT id_token returned from Google. However, the documentation seems inconsistent about what value Google returns for the iss (issuer) claim in the ID token.

One page says, "iss: always accounts.google.com", but another page says "The value of iss in the ID token is equal to accounts.google.com or https://accounts.google.com" and a comment in the example code further explains:

// If you retrieved the token on Android using the Play Services 8.3 API or newer, set
// the issuer to "https://accounts.google.com". Otherwise, set the issuer to
// "accounts.google.com". If you need to verify tokens from multiple sources, build
// a GoogleIdTokenVerifier for each issuer and try them both.

I have a server-side application, not an Android app, so I'm not using Play Services.

To further muddy the waters, the OpenIDConnect specification itself contains a note that:

Implementers may want to be aware that, as of the time of this writing, Google's deployed OpenID Connect implementation issues ID Tokens that omit the required https:// scheme prefix from the iss (issuer) Claim Value. Relying Party implementations wishing to work with Google will therefore need to have code to work around this, until such time as their implementation is updated. Any such workaround code should be written in a manner that will not break at such point Google adds the missing prefix to their issuer values.

That document is dated November 8, 2014. In the time since then, has Google standardized on an iss value, or do I really need to check for both of them? The comment above seems to indicate that only Play Services >=8.3 gets iss with https://, and everywhere else the value will be just accounts.google.com. Is that true?


Solution

  • You have to check both possibilities. This is what worked for me...

    Decode the token to get the issuer. If the issuer is not equal to either one of https://accounts.google.com or accounts.google.com you can stop there. It's an invalid token.

    If the issuer is equal to either of the above Google strings, then pass that same decoded issuer value forward to the verification step.

    Following is the an implementation I wrote in JavaScript for some Node.js Express middleware:

    function authorize(req, res, next) {
        try {
            var token       = req.headers.authorization;
            var decoded     = jwt.decode(token, { complete: true });
            var keyID       = decoded.header.kid;
            var algorithm   = decoded.header.alg;
            var pem         = getPem(keyID);
            var iss         = decoded.payload.iss;
    
            if (iss === 'accounts.google.com' || iss === 'https://accounts.google.com') {
                var options = {
                    audience: CLIENT_ID,
                    issuer: iss,
                    algorithms: [algorithm]
                }
    
                jwt.verify(token, pem, options, function(err) {
                    if (err) {
                        res.writeHead(401);
                        res.end();
                    } else {
                        next();
                    }
                });            
    
            } else {
                res.writeHead(401);
                res.end();
            }
        } catch (err) {
            res.writeHead(401);
            res.end();
        }
    }
    

    Note this function uses jsonwebtoken and jwk-to-pem node modules. I ommitted details of the getPem function which ultimately converts a json web key to pem format.