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

eugenioy's answer stopped working for me because of this refactor. I ended up fixing with something like this

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    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 len(keys) == 0 {
         return nil, fmt.Errorf("key %v not found", kid)
    }
    // keys[0].Materialize() doesn't exist anymore
    var raw interface{}
    return raw, keys[0].Raw(&raw)
})


This is what I did with only the latest (v1.0.8) github.com/lestrrat-go/jwx. Note that github.com/dgrijalva/jwt-go does not seem to be maintained anymore and people are forking it to make the updates they need.

package main

import (
    ...
    "github.com/lestrrat-go/jwx/jwk"
    "github.com/lestrrat-go/jwx/jwt"
)
    ...

    keyset, err := jwk.Fetch("https://cognito-idp." + region + ".amazonaws.com/" + userPoolID + "/.well-known/jwks.json")

    parsedToken, err := jwt.Parse(
        bytes.NewReader(token), //token is a []byte
        jwt.WithKeySet(keyset),
        jwt.WithValidate(true),
        jwt.WithIssuer(...),
        jwt.WithClaimValue("key", value),
    )

    //check err as usual
    //here you can call methods on the parsedToken to get the claim values
    ...

Token claim methods


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
    

The type assertion in the code provided by eugenioy and Kevin Wydler did not work for me: *jwt.SigningMethodRS256 is not a type.

*jwt.SigningMethodRS256 was a type in the initial commit. From the second commit on (back in July 2014) it was abstracted and replaced by a global variable (see here).

This following code works for me:

func verify(tokenString string, keySet *jwk.Set) {
  tkn, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    if token.Method.Alg() != "RSA256" { // jwa.RS256.String() works as well
      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 len(keys) == 0 {
      return nil, fmt.Errorf("key %v not found", kid)
    }
    var raw interface{}
    return raw, keys[0].Raw(&raw)
  })
}

Using the following dependency versions:

github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1
github.com/lestrrat-go/jwx v1.0.4