When I download the JWT set for a particular User Pool available at: https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
The JSON contains 2 keys. All the users that I have created for the pool seem to use just one of these keys.
What is the reason for having multiple keys per User Pool?
In each Region, Amazon Cognito is distributed across multiple Availability Zones. These Availability Zones are physically isolated from each other, but are united by private, low-latency, high-throughput, and highly redundant network connections.
Cognito Identity does not receive or store user credentials. Cognito Identity uses the token from the identity provider to obtain a unique identifier for the user and then hashes it using a one-way hash so that the same user can be recognized again in the future without storing the actual user identifier.
After a user logs in, an Amazon Cognito user pool returns a JWT. The JWT is a base64url-encoded JSON string ("claims") that contains information about the user. Amazon Cognito returns three tokens: the ID token, the access token, and the refresh token.
The ID token contains claims about the identity of the authenticated user, such as name and email. The access token contains claims about the authenticated user, a list of the user's groups, and a list of scopes. Amazon Cognito also has tokens that you can use to get new tokens or revoke existing tokens.
According to the documentation:
Amazon Cognito generates two pairs of RSA cryptograpic keys for each user pool. One of the private keys is used to sign the token.
Presumably this is for security reasons. From limited trial-and-error it appears that one is used to encrypt id tokens and the other is used to encrypt access tokens. Or perhaps the purpose is to support key rotation (as suggested by @Michael-sqlbot).
Once you understand the why (or don't understand), the question becomes what to do with the two keys.
Again referring to the documentation, to validate the JWT signature you need to:
Steps 1 and 2 are necessary to figure out which RSA cryptograpic key was used to encrypt your JWT.
import jsonwebtoken from 'jsonwebtoken'
import jwkToPem from 'jwk-to-pem'
const jsonWebKeys = [ // from https://cognito-idp.us-west-2.amazonaws.com/<UserPoolId>/.well-known/jwks.json
{
"alg": "RS256",
"e": "AQAB",
"kid": "ABCDEFGHIJKLMNOPabc/1A2B3CZ5x6y7MA56Cy+6ubf=",
"kty": "RSA",
"n": "...",
"use": "sig"
},
{
"alg": "RS256",
"e": "AQAB",
"kid": "XYZAAAAAAAAAAAAAAA/1A2B3CZ5x6y7MA56Cy+6abc=",
"kty": "RSA",
"n": "...",
"use": "sig"
}
]
function validateToken(token) {
const header = decodeTokenHeader(token) // {"kid":"XYZAAAAAAAAAAAAAAA/1A2B3CZ5x6y7MA56Cy+6abc=", "alg": "RS256"}
const jsonWebKey = getJsonWebKeyWithKID(header.kid)
verifyJsonWebTokenSignature(token, jsonWebKey, function(err, decodedToken) {
if (err) {
console.error(err)
} else {
console.log(decodedToken)
}
})
}
function decodeTokenHeader(token) {
const [headerEncoded] = token.split('.')[0]
const buff = new Buffer(headerEncoded, 'base64')
const text = buff.toString('ascii')
return JSON.parse(text)
}
func getJsonWebKeyWithKID(kid) {
for (let jwk of jsonWebKeys) {
if (jwk.kid == kid) {
return jwk
}
}
return null
}
function verifyJsonWebTokenSignature(token, jsonWebKey, clbk) {
const pem = jwkToPem(jsonWebKey)
jsonwebtoken.verify(token, pem, { algorithms: ['RS256'] }, function(err, decodedToken) {
return clbk(err, decodedToken)
})
}
validateToken('xxxxxxxxx.XXXXXXXX.xxxxxxxx')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With