-
Notifications
You must be signed in to change notification settings - Fork 211
/
jwt.ts
88 lines (77 loc) · 2.66 KB
/
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
import { KJUR, KEYUTIL, ArrayBuffertohex } from 'jsrsasign'
import jwt_decode from 'jwt-decode'
import { Base64 } from 'js-base64'
import * as ccf from '../types/ccf'
interface JwtResponse {
userId: string
}
interface ErrorResponse {
msg: string
}
interface HeaderClaims {
kid: string
}
interface BodyClaims {
sub: string
}
export function jwt(request: ccf.Request): ccf.Response<JwtResponse | ErrorResponse> {
const authHeader = request.headers['authorization']
if (!authHeader) {
return unauthorized('authorization header missing')
}
const parts = authHeader.split(' ', 2)
if (parts.length !== 2 || parts[0] !== 'Bearer') {
return unauthorized('unexpected authentication type')
}
const token = parts[1]
// Extract header claims to select the correct signing key.
// We use jwt_decode() instead of jsrsasign's parse() as the latter does unnecessary work.
let headerClaims: HeaderClaims
try {
headerClaims = jwt_decode(token, { header: true }) as HeaderClaims
} catch (e) {
return unauthorized(`malformed jwt: ${e.message}`)
}
const signingKeyId = headerClaims.kid
if (!signingKeyId) {
return unauthorized('kid missing in header claims')
}
// Get the stored signing key to validate the token.
const keysMap = new ccf.TypedKVMap(ccf.kv['public:ccf.gov.jwt_public_signing_keys'], ccf.string, ccf.typedArray(Uint8Array))
const publicKeyDer = keysMap.get(signingKeyId)
if (publicKeyDer === undefined) {
return unauthorized(`token signing key not found: ${signingKeyId}`)
}
// jsrsasign can only load X.509 certs from PEM strings
const publicKeyB64 = Base64.fromUint8Array(publicKeyDer)
const publicKeyPem = "-----BEGIN CERTIFICATE-----\n" + publicKeyB64 + "\n-----END CERTIFICATE-----";
const publicKey = KEYUTIL.getKey(publicKeyPem)
// Validate the token signature.
const valid = KJUR.jws.JWS.verifyJWT(token, <any>publicKey, <any>{
alg: ['RS256'],
// No trusted time, disable time validation.
verifyAt: Date.parse('2020-01-01T00:00:00') / 1000,
gracePeriod: 10 * 365 * 24 * 60 * 60
})
if (!valid) {
return unauthorized('jwt validation failed')
}
// Custom body claims validation, app-specific.
const claims = jwt_decode(token) as BodyClaims
if (!claims.sub) {
return unauthorized('jwt invalid, sub claim missing')
}
return {
body: {
userId: claims.sub
}
}
}
function unauthorized(msg: string): ccf.Response<ErrorResponse> {
return {
statusCode: 401,
body: {
msg: msg
}
}
}