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:
Download and parse the public keys JSON using the first library
keySet, err := jwk.Fetch(THE_COGNITO_URL_DESCRIBED_ABOVE)
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