Validating Google OpenID Connect JWT ID Token

.net, c#, jwt, openid-connect, owin

Solution

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); };

Problem

I'm trying to upgrade my MVC website to use the new OpenID Connect standard. The OWIN middleware seems to be pretty robust, but unfortunately only supports the "form_post" response type. This means that Google isn't compatible, as it returns all the tokens in a the url after a "#", so they never reach the server and never trigger the middleware. I've tried to trigger the response handlers in the middleware myself, but that doesn't seem to work at all, so I've got a simply javascript file that parses out the returned claims and POSTs them to a controller action for processing. Problem is, even when I get them on the server side I can't parse them correctly. The error I get looks like this: ``` IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier: 'SecurityKeyIdentifier ( IsReadOnly = False, Count = 1, Clause[0] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause ), token: '{ "alg":"RS256", "kid":"073a3204ec09d050f5fd26460d7ddaf4b4ec7561" }. { "iss":"accounts.google.com", "sub":"100330116539301590598", "azp":"1061880999501-b47blhmmeprkvhcsnqmhfc7t20gvlgfl.apps.googleusercontent.com", "nonce":"7c8c3656118e4273a397c7d58e108eb1", "email_verified":true, "aud":"1061880999501-b47blhmmeprkvhcsnqmhfc7t20gvlgfl.apps.googleusercontent.com", "iat":1429556543,"exp\":1429560143 }'." } ``` My token verification code follows the example outlined by the good people developing IdentityServer ``` private async Task<IEnumerable<Claim>> ValidateIdentityTokenAsync(string idToken, string state) { // New Stuff var token = new JwtSecurityToken(idToken); var jwtHandler = new JwtSecurityTokenHandler(); byte[][] certBytes = getGoogleCertBytes(); for (int i = 0; i < certBytes.Length; i++) { var certificate = new X509Certificate2(certBytes[i]); var certToken = new X509SecurityToken(certificate); // Set up token validation var tokenValidationParameters = new TokenValidationParameters(); tokenValidationParameters.ValidAudience = googleClientId; tokenValidationParameters.IssuerSigningToken = certToken; tokenValidationParameters.ValidIssuer = "accounts.google.com"; try { // Validate SecurityToken jwt; var claimsPrincipal = jwtHandler.ValidateToken(idToken, tokenValidationParameters, out jwt); if (claimsPrincipal != null) { // Valid idTokenStatus = "Valid"; } } catch (Exception e) { if (idTokenStatus != "Valid") { // Invalid? } } } return token.Claims; } private byte[][] getGoogleCertBytes() { // The request will be made to the authentication server. WebRequest request = WebRequest.Create( "https://www.googleapis.com/oauth2/v1/certs" ); StreamReader reader = new StreamReader(request.GetResponse().GetResponseStream()); string responseFromServer = reader.ReadToEnd(); String[] split = responseFromServer.Split(':'); // There are two certificates returned from Google byte[][] certBytes = new byte[2][]; int index = 0; UTF8Encoding utf8 = new UTF8Encoding(); for (int i = 0; i < split.Length; i++) { if (split[i].IndexOf(beginCert) > 0) { int startSub = split[i].IndexOf(beginCert); int endSub = split[i].IndexOf(endCert) + endCert.Length; certBytes[index] = utf8.GetBytes(split[i].Substring(startSub, endSub).Replace("\\n", "\n")); index++; } } return certBytes; } ``` I know that Signature validation isn't completely necessary for JWTs but I haven't the slightest idea how to turn it off. Any ideas?

Original source