Last active
August 27, 2019 08:43
-
-
Save bradennapier/c996672944ebf511410eb45c0395f525 to your computer and use it in GitHub Desktop.
Just a rough sketch for validating cognito tokens
This file contains hidden or 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
/* @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