Created
August 15, 2022 09:34
-
-
Save SamWSoftware/b4375a7dcbbd57fc1931b798afc09423 to your computer and use it in GitHub Desktop.
Cognito Token Verification
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
// https://gist.github.com/SamWSoftware/b4375a7dcbbd57fc1931b798afc09423 | |
import { promisify } from 'util'; | |
import * as Axios from 'axios'; | |
import * as jsonwebtoken from 'jsonwebtoken'; | |
const jwkToPem = require('jwk-to-pem'); | |
export interface ClaimVerifyRequest { | |
readonly token: string; | |
} | |
export interface ClaimVerifyResult { | |
readonly userName: string; | |
readonly userId: string; | |
readonly clientId: string; | |
readonly isValid: boolean; | |
readonly error?: any; | |
} | |
interface TokenHeader { | |
kid: string; | |
alg: string; | |
} | |
interface PublicKey { | |
alg: string; | |
e: string; | |
kid: string; | |
kty: string; | |
n: string; | |
use: string; | |
} | |
interface PublicKeyMeta { | |
instance: PublicKey; | |
pem: string; | |
} | |
interface PublicKeys { | |
keys: PublicKey[]; | |
} | |
interface MapOfKidToPublicKey { | |
[key: string]: PublicKeyMeta; | |
} | |
interface Claim { | |
token_use: string; | |
auth_time: number; | |
iss: string; | |
exp: number; | |
client_id: string; | |
'cognito:username': string; | |
email: string; | |
} | |
const cognitoPoolId = process.env.COGNITO_POOL_ID || ''; | |
const region = process.env.region; | |
if (!cognitoPoolId) { | |
throw new Error('env var required for cognito pool'); | |
} | |
const cognitoIssuer = `https://cognito-idp.${region}.amazonaws.com/${cognitoPoolId}`; | |
let cacheKeys: MapOfKidToPublicKey | undefined; | |
const getPublicKeys = async (): Promise<MapOfKidToPublicKey> => { | |
if (!cacheKeys) { | |
const url = `${cognitoIssuer}/.well-known/jwks.json`; | |
const publicKeys = await Axios.default.get<PublicKeys>(url); | |
cacheKeys = publicKeys.data.keys.reduce((agg, current) => { | |
const pem = jwkToPem(current); | |
agg[current.kid] = { instance: current, pem }; | |
return agg; | |
}, {} as MapOfKidToPublicKey); | |
return cacheKeys; | |
} else { | |
return cacheKeys; | |
} | |
}; | |
const verifyPromised = promisify(jsonwebtoken.verify.bind(jsonwebtoken)); | |
export const verifyToken = async ( | |
request: ClaimVerifyRequest | |
): Promise<ClaimVerifyResult> => { | |
let result: ClaimVerifyResult; | |
try { | |
const token = request.token; | |
const tokenSections = (token || '').split('.'); | |
if (tokenSections.length < 2) { | |
throw new Error('requested token is invalid'); | |
} | |
const headerJSON = Buffer.from(tokenSections[0], 'base64').toString('utf8'); | |
const header = JSON.parse(headerJSON) as TokenHeader; | |
const keys = await getPublicKeys(); | |
console.log('got public keys'); | |
const key = keys[header.kid]; | |
if (key === undefined) { | |
throw new Error('claim made for unknown kid'); | |
} | |
const claim = (await verifyPromised(token, key.pem)) as Claim; | |
console.log('claim', claim); | |
const currentSeconds = Math.floor(new Date().valueOf() / 1000); | |
if (currentSeconds > claim.exp || currentSeconds < claim.auth_time) { | |
throw new Error('claim is expired or invalid'); | |
} | |
if (claim.iss !== cognitoIssuer) { | |
throw new Error('claim issuer is invalid'); | |
} | |
console.log(`claim confirmed for ${claim.email}`); | |
result = { | |
userName: claim.email, | |
userId: claim['cognito:username'], | |
clientId: claim.client_id, | |
isValid: true, | |
}; | |
} catch (error) { | |
result = { userName: '', userId: '', clientId: '', error, isValid: false }; | |
} | |
return result; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment