Last active
August 25, 2024 01:23
-
-
Save tomas-chudjak/d0123ca5f6461bc8e220063e8a4e60c8 to your computer and use it in GitHub Desktop.
How to parse and validate AWS Cognito token with go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// AWS Cognito public keys are available at address: | |
// https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json | |
publicKeysURL := "https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json" | |
// Start with downloading public keys information | |
// The .Fetch method is used from https://github.com/lestrrat-go/jwx package | |
publicKeySet, err := jwk.Fetch(publicKeysURL) | |
if err != nil{ | |
log.Printf("failed to parse key: %s", err) | |
} | |
// Get access token as string from *principal | |
// Access token is Base64-encoded JSON that contains user details - called "claims". | |
// --- | |
// Token is separated into 3 sections - header, payload and signature | |
// You can test and validate your token with jwt.io | |
tokenString := fmt.Sprintf("%s", *principal) | |
// We want to get details from the access token: client_id and unique user identifier. | |
// Let's add client_id. We can verify, if it match our App cliet ID in AWS Cognito User Pool | |
// We can also add user identifier (f.e. "username") to use it with our App | |
type AWSCognitoClaims struct{ | |
Client_ID string `json:client_id` | |
Username string `json:username` | |
jwt.StandardClaims | |
} | |
// JWT Parse - it's actually doing parsing, validation and returns back a token. | |
// Use .Parse or .ParseWithClaims methods from https://github.com/dgrijalva/jwt-go | |
token, err := jwt.ParseWithClaims(tokenString, &AWSCognitoClaims{}, func(token *jwt.Token) (interface{}, error){ | |
// Verify if the token was signed with correct signing method | |
// AWS Cognito is using RSA256 in my case | |
_, ok := token.Method.(*jwt.SigningMethodRSA); | |
if !ok { | |
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) | |
} | |
// Get "kid" value from token header | |
// "kid" is shorthand for Key ID | |
kid, ok := token.Header["kid"].(string) | |
if !ok { | |
return nil, errors.New("kid header not found") | |
} | |
// Check client_id attribute from the access token | |
claims, ok := token.Claims.(*AWSCognitoClaims) | |
if !ok { | |
return nil, errors.New("There is problem to get claims") | |
} | |
log.Printf("client_id: %v", claims.Client_ID) | |
// "kid" must be present in the public keys set | |
keys := publicKeySet.LookupKeyID(kid); | |
if len(keys) == 0 { | |
return nil, fmt.Errorf("key %v not found", kid) | |
} | |
// In our case, we are returning only one key = keys[0] | |
// Return token key as []byte{string} type | |
var tokenKey interface{} | |
if err := keys[0].Raw(&tokenKey); err != nil { | |
return nil, errors.New("failed to create token key") | |
} | |
return tokenKey, nil | |
}) | |
if err != nil{ | |
// This place can throw expiration error | |
log.Printf("token problem: %s", err) | |
} | |
// Check if token is valid | |
if !token.Valid { | |
log.Println("token is invalid") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment