-
Notifications
You must be signed in to change notification settings - Fork 801
/
Copy pathdecode-verify-jwt.ts
110 lines (99 loc) · 3.02 KB
/
decode-verify-jwt.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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 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;
username: string;
client_id: string;
}
const cognitoPoolId = process.env.COGNITO_POOL_ID || '';
if (!cognitoPoolId) {
throw new Error('env var required for cognito pool');
}
const cognitoIssuer = `https://cognito-idp.us-east-1.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));
const handler = async (request: ClaimVerifyRequest): Promise<ClaimVerifyResult> => {
let result: ClaimVerifyResult;
try {
console.log(`user claim verify invoked for ${JSON.stringify(request)}`);
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();
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;
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');
}
if (claim.token_use !== 'access') {
throw new Error('claim use is not access');
}
console.log(`claim confirmed for ${claim.username}`);
result = {userName: claim.username, clientId: claim.client_id, isValid: true};
} catch (error) {
result = {userName: '', clientId: '', error, isValid: false};
}
return result;
};
export {handler};