Validating Google OpenID Connect JWT ID Token

I thought I'd post my slightly improved version which uses JSON.Net to parse Googles' X509 Certificates and matches the key to use based on the "kid" (key-id). This is a bit more efficient than trying each certificate, since asymmetric crypto is usually quite expensive.

Also removed out-dated WebClient and manual string parsing code:

    static Lazy<Dictionary<string, X509Certificate2>> Certificates = new Lazy<Dictionary<string, X509Certificate2>>( FetchGoogleCertificates );
    static Dictionary<string, X509Certificate2> FetchGoogleCertificates()
    {
        using (var http = new HttpClient())
        {
            var json = http.GetStringAsync( "https://www.googleapis.com/oauth2/v1/certs" ).Result;

            var dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>( json );
            return dictionary.ToDictionary( x => x.Key, x => new X509Certificate2( Encoding.UTF8.GetBytes( x.Value ) ) );
        }
    }

    JwtSecurityToken ValidateIdentityToken( string idToken )
    {
        var token = new JwtSecurityToken( idToken );
        var jwtHandler = new JwtSecurityTokenHandler();

        var certificates = Certificates.Value;

        try
        {
            // Set up token validation
            var tokenValidationParameters = new TokenValidationParameters();
            tokenValidationParameters.ValidAudience = _clientId;
            tokenValidationParameters.ValidIssuer = "accounts.google.com";
            tokenValidationParameters.IssuerSigningTokens = certificates.Values.Select( x => new X509SecurityToken( x ) );
            tokenValidationParameters.IssuerSigningKeys = certificates.Values.Select( x => new X509SecurityKey( x ) );
            tokenValidationParameters.IssuerSigningKeyResolver = ( s, securityToken, identifier, parameters ) =>
            {
                return identifier.Select( x =>
                {
                    if (!certificates.ContainsKey( x.Id ))
                        return null;

                    return new X509SecurityKey( certificates[ x.Id ] );
                } ).First( x => x != null );
            };

            SecurityToken jwt;
            var claimsPrincipal = jwtHandler.ValidateToken( idToken, tokenValidationParameters, out jwt );
            return (JwtSecurityToken)jwt;
        }
        catch (Exception ex)
        {
            _trace.Error( typeof( GoogleOAuth2OpenIdHybridClient ).Name, ex );
            return null;
        }
    }

The problem is the kid in the JWT whose value is the key identifier of the key was used to sign the JWT. Since you construct an array of certificates manually from the JWKs URI, you lose the key identifier information. The validation procedure however requires it.

You'll need to set tokenValidationParameters.IssuerSigningKeyResolver to a function that will return the same key that you set above in tokenValidationParameters.IssuerSigningToken. The purpose of this delegate is to instruct the runtime to ignore any 'matching' semantics and just try the key.

See this article for more information: JwtSecurityTokenHandler 4.0.0 Breaking Changes?

Edit: the code:

tokenValidationParameters.IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) => { return new X509SecurityKey(certificate); };