Verifying AWS Cognito OIDC ID Token
The ID token proved to your application upon a user logging into the Cognito SSO, is actually a JSON Web Token (JWT) This is a requirement from the OIDC specification. You can convert it to the underlying JSON by using a JWT debugger such as jwt.io.
Retrieving The JWK For Verification
In order to verify the JWT, in order to be sure that someone did legitimately log in through the third-party service, and not just hit your endpoint with a made-up ID token, you need to have your JSON Web Key (JWK). We use this key to reproduce the signature, and if it matches the one provided, we know that the JWT is legitimate. If using Amazon Cognito, you can retrieve the JWK by going to:
$jsonWebKeyUrl = "https://cognito-idp.{$region}.amazonaws.com/{$userPoolId}/.well-known/jwks.json";
$region
would be something like eu-west-2
for London. You will need to retrieve the user pool ID from the web console, or Terraform (however you set up the user pool).
This URL will actually give you a pair of keys JSON format like so:
{
"keys": [
{
"alg": "RS256",
"e": "XXXX",
"kid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=",
"kty": "RSA",
"n": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"use": "sig"
},
{
"alg": "RS256",
"e": "XXXX",
"kid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=",
"kty": "RSA",
"n": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"use": "sig"
}
]
}
You will know which key you need by the kid
matching up with the one provided in the kid
attribute of the JWT header.
Using PHP Package For JWT Verification
Since we do not wish to have to manually perform the many complicated operations for re-generating and verifying the signature, we can use the firebase JWT package to do this for us like so:
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
// Build up the cognito URL to get the JWK from
$region = AWS_REGION;
$userPoolId = USER_POOL_ID
$jsonWebKeyUrl = "https://cognito-idp.{$region}.amazonaws.com/{$userPoolId}/.well-known/jwks.json";
// Laravel HTTP GET request to array
// You probably want to cache this result array
$jsonWebKeys = Http::get($jsonWebKeyUrl)->json();
// Finally, use the key to verify and decode the ID token JWT
$firebaseKeys = JWK::parseKeySet($jsonWebKeys);
$decodedPayload = JWT::decode($idToken, $firebaseKeys); // throws SignatureInvalidException if invalid signature given
Decoded And Verified JWT
That's it! You have now verified the JWT (will have thrown exception if invalid), and have the decoded payload contents in the $decodedPayload
variable.
Below is some sample output of that variable if you were to JSON pretty-print it.
{
"at_hash": "xxxxxxxxxxxxxxxxxxxxxx",
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"cognito:groups": [
"group1",
"group2"
],
"email_verified": true,
"iss": "https:\/\/cognito-idp.eu-west-2.amazonaws.com\/eu-west-2_xxxxxxxxx",
"cognito:username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"origin_jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"aud": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"token_use": "id",
"auth_time": 1650901990,
"name": "Programster Page",
"exp": 1650905590,
"iat": 1650901990,
"jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"email": "my.email@gmail.com"
}
In here, you can see some really useful information, such as the user's email address, ID (referred to as sub
for subject), and the groups the user is part of (cognito:groups
).
Those groups can be particularly useful for determining what the user should or should not be able to do within your application.
References
- AWS Docs - Amazon Cognito - Using the ID token
- New – Amazon Cognito Groups and Fine-Grained Role-Based Access Control
First published: 25th April 2022