How to verify a JWT Token from AWS Cognito in Go?

Lewis Lebentz picture Lewis Lebentz · Jul 5, 2019 · Viewed 6.9k times · Source

How can I validate and get info from a JWT received from Amazon Cognito?

I have setup Google authentication in Cognito, and set the redirect uri to to hit API Gateway, I then receive a code which I POST to this endpoint:

https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html

To receive the JWT token, in a RS256 format. I am now struggling to validate, and parse the token in Golang. I’ve tried to parse it using jwt-go, but it appears to support HMAC instead by default and read somewhere that they recommend using frontend validation instead. I tried a few other packages and had similar problems.

I came across this answer here: Go Language and Verify JWT but assume the code is outdated as that just says panic: unable to find key.

jwt.io can easily decode the key, and probably verify too. I’m not sure where the public/secret keys are as Amazon generated the token, but from what I understand I need to use a JWK URL to validate too? I’ve found a few AWS specific solutions, but they all seem to be hundreds of lines long. Surely it isn’t that complicated in Golang is it?

Answer

eugenioy picture eugenioy · Jul 5, 2019

Public keys for Amazon Cognito

As you already guessed, you'll need the public key in order to verify the JWT token.

https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html#amazon-cognito-user-pools-using-tokens-step-2

Download and store the corresponding public JSON Web Key (JWK) for your user pool. It is available as part of a JSON Web Key Set (JWKS). You can locate it at https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json

Parse keys and verify token

That JSON file structure is documented in the web, so you could potentially parse that manually, generate the public keys, etc.

But it'd probably be easier to just use a library, for example this one: https://github.com/lestrrat-go/jwx

And then jwt-go to deal with the JWT part: https://github.com/dgrijalva/jwt-go

You can then:

  1. Download and parse the public keys JSON using the first library

     keySet, err := jwk.Fetch(THE_COGNITO_URL_DESCRIBED_ABOVE)
    
  2. When parsing the token with jwt-go, use the "kid" field from the JWT header to find the right key to use

     token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
     if _, ok := token.Method.(*jwt.SigningMethodRS256); !ok {
         return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
     }
     kid, ok := token.Header["kid"].(string)
     if !ok {
         return nil, errors.New("kid header not found")
     }
     keys := keySet.LookupKeyID(kid);
     if !ok {
         return nil, fmt.Errorf("key with specified kid is not present in jwks")
     }
     var publickey interface{}
     err = keys.Raw(&publickey)
     if err != nil {
         return nil, fmt.Errorf("could not parse pubkey")
     }
     return publickey, nil