Skip to content

Instantly share code, notes, and snippets.

@bradennapier
Last active August 27, 2019 08:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bradennapier/c996672944ebf511410eb45c0395f525 to your computer and use it in GitHub Desktop.
Save bradennapier/c996672944ebf511410eb45c0395f525 to your computer and use it in GitHub Desktop.
Just a rough sketch for validating cognito tokens
/* @flow */
import axios from 'axios';
import jose from 'node-jose';
import day from 'dayjs';
const AWS_REGION = '<AWS_REGION>';
const AWS_POOL_ID = '<POOL_ID>';
const AWS_APP_CLIENT_ID = '<APP_CLIENT_ID>';
const AWS_TOKEN_PUBLIC_KEYS_URL =
`https://cognito-idp.${AWS_REGION}.amazonaws.com/${AWS_POOL_ID}/.well-known/jwks.json`;
const REFRESH_TOKEN_EVERY_N_HOURS = 4;
let PUBLIC_KEY_DATA;
let LAST_REFRESHED;
type ValidateTokenOptions = {
/**
* Should we check the expiration while validating?
*/
checkExpiration: boolean,
};
async function getPublicKeyData() {
try {
const now = day();
if (
PUBLIC_KEY_DATA &&
LAST_REFRESHED &&
now.diff(LAST_REFRESHED, 'hours') < REFRESH_TOKEN_EVERY_N_HOURS
) {
return PUBLIC_KEY_DATA;
}
const { data } = await axios.get(AWS_TOKEN_PUBLIC_KEYS_URL);
if (data) {
LAST_REFRESHED = now;
PUBLIC_KEY_DATA = data;
}
return PUBLIC_KEY_DATA;
} catch (err) {
console.error(
'[ERROR] | validateToken | Failed to capture public key from url: ',
AWS_TOKEN_PUBLIC_KEYS_URL,
err,
);
throw err;
}
}
const DEFAULT_VALIDATE_OPTIONS: ValidateTokenOptions = {
checkExpiration: true,
};
export default async function validateCognitoToken(
token: string,
opts?: $Shape<ValidateTokenOptions>,
) {
if (!token) {
console.error('No Token provided to validateCognitoToken');
throw new Error('Failed to validate request [0]');
}
const options: ValidateTokenOptions = Object.assign(
{},
DEFAULT_VALIDATE_OPTIONS,
opts || {},
);
const sections = token.split('.');
const { kid } = JSON.parse(jose.util.base64url.decode(sections[0]));
if (kid) {
const { keys } = await getPublicKeyData();
const key = keys.find(k => k.kid === kid);
if (!key) {
console.warn(
'[WARN] | validateToken | Unknown "kid" value received in jwt token: ',
kid,
);
throw new Error('Failed to validate request [1]');
}
const result = await jose.JWS.createVerify(
await jose.JWK.asKey(key),
).verify(token);
const claims = JSON.parse(result.payload);
if (options.checkExpiration && Date.now() / 1000 > claims.exp) {
throw new Error('Token expired');
}
if (options.checkAud && (claims.aud !== AWS_APP_CLIENT_ID && claims.client_id !== AWS_APP_CLIENT_ID)) {
throw new Error('Failed to validate request [2]');
}
return claims;
}
throw new Error('Failed to validate request [3]');
}
/*
token claims provide the following data at this time:
{ sub: '<masked>',
aud: '<AWS_APP_CLIENT_ID>',
event_id: '<masked>',
token_use: 'id',
auth_time: 1554961286,
iss:
'<masked>',
'cognito:username': '<masked>',
exp: 1554964887,
iat: 1554961287,
email: '<masked>' }
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment