| 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 |
1x
1x
1x
1x
1x
1x
12x
7x
7x
7x
7x
7x
7x
7x
7x
7x
7x
7x
7x
7x
7x
7x
7x
4x
4x
4x
4x
4x
3x
3x
3x
3x
1x
1x
1x
1x
4x
4x
4x
4x
4x
4x
4x
4x
8x
4x
4x
4x
4x
4x
4x
7x
7x
1x
1x
1x
| /**
* Local dependencies
*/
const crypto = require('@trust/webcrypto')
const base64url = require('base64url')
const {JWT} = require('@trust/jose')
const IDTokenSchema = require('./schemas/IDTokenSchema')
const DEFAULT_MAX_AGE = 3600 // Default ID token expiration, in seconds
const DEFAULT_SIG_ALGORITHM = 'RS256'
/**
* IDToken
*/
class IDToken extends JWT {
/**
* Schema
*/
static get schema () {
return IDTokenSchema
}
/**
* issue
*
* @param provider {Provider} OIDC Identity Provider issuing the token
* @param provider.issuer {string} Provider URI
* @param provider.keys {KeyChain}
*
* @param options {Object}
* @param options.aud {string|Array<string>} Audience for the token
* (such as the Relying Party client_id)
* @param options.sub {string} Subject id for the token (opaque, unique to
* the issuer)
* @param options.nonce {string} Nonce generated by Relying Party
*
* Optional:
* @param [options.alg] {string} Algorithm for signing the id token
* @param [options.jti] {string} Unique JWT id (to prevent reuse)
* @param [options.iat] {number} Issued at timestamp (in seconds)
* @param [options.max] {number} Max token lifetime in seconds
* @param [options.at_hash] {string} Access Token Hash
* @param [options.c_hash] {string} Code hash
*
* @returns {IDToken} ID Token (JWT instance)
*/
static issue (provider, options) {
let { issuer, keys } = provider
let { aud, sub, nonce, at_hash, c_hash } = options
let alg = options.alg || DEFAULT_SIG_ALGORITHM
let jti = options.jti || IDToken.random(8)
let iat = options.iat || Math.floor(Date.now() / 1000)
let max = options.max || DEFAULT_MAX_AGE
let exp = iat + max // token expiration
let iss = issuer
let key = keys['id_token'].signing[alg].privateKey
let kid = keys['id_token'].signing[alg].publicJwk.kid
let header = { alg, kid }
let payload = { iss, aud, sub, exp, iat, jti, nonce }
if (at_hash) { payload.at_hash = at_hash }
if (c_hash) { payload.c_hash = c_hash }
let jwt = new IDToken({ header, payload, key })
return jwt
}
/**
* issueForRequest
*/
static issueForRequest (request, response) {
let {params, code, provider, client, subject} = request
let alg = client['id_token_signed_response_alg'] || DEFAULT_SIG_ALGORITHM
let jti = IDToken.random(8)
let iat = Math.floor(Date.now() / 1000)
let aud, sub, max, nonce
// authentication request
if (!code) {
aud = client['client_id']
sub = subject['_id']
max = parseInt(params['max_age']) || client['default_max_age'] || DEFAULT_MAX_AGE
nonce = params.nonce
// token request
} else {
aud = code.aud
sub = code.sub
max = parseInt(code['max']) || client['default_max_age'] || DEFAULT_MAX_AGE
nonce = code.nonce
}
let len = alg.match(/(256|384|512)$/)[0]
// generate hashes
return Promise.all([
IDToken.hashClaim(response['access_token'], len),
IDToken.hashClaim(response['code'], len)
])
// build the id_token
.then(hashes => {
let [at_hash, c_hash] = hashes
let options = { alg, aud, sub, iat, jti, nonce, at_hash, c_hash }
return IDToken.issue(provider, options)
})
// sign id token
.then(jwt => jwt.encode())
// add to response
.then(compact => {
response['id_token'] = compact
})
// resolve the response
.then(() => response)
}
/**
* hashClaim
*
* @description
* Create a hash for at_hash or c_hash claim
*
* @param {string} token
* @param {string} hashLength
*
* @returns {Promise<string>}
*/
static hashClaim (value, hashLength) {
if (value) {
let alg = { name: `SHA-${hashLength}`}
let octets = new Buffer(value, 'ascii')
return crypto.subtle.digest(alg, new Uint8Array(octets)).then(digest => {
let hash = Buffer.from(digest)
let half = hash.slice(0, hash.byteLength / 2)
return base64url(half)
})
}
}
static random (byteLen) {
let value = crypto.getRandomValues(new Uint8Array(byteLen))
return Buffer.from(value).toString('hex')
}
}
IDToken.DEFAULT_MAX_AGE = DEFAULT_MAX_AGE
IDToken.DEFAULT_SIG_ALGORITHM = DEFAULT_SIG_ALGORITHM
/**
* Export
*/
module.exports = IDToken
|