1
1
import crypto from "crypto"
2
- import jose from "jose"
3
- import logger from "../lib/logger "
2
+ import { EncryptJWT , jwtDecrypt } from "jose"
3
+ import uuid from "uuid "
4
4
import { NextApiRequest } from "next"
5
- import type { JWT , JWTDecodeParams , JWTEncodeParams } from "./types"
5
+ import type { JWT , JWTDecodeParams , JWTEncodeParams , JWTOptions } from "./types"
6
6
7
7
export * from "./types"
8
8
9
- // Set default algorithm to use for auto-generated signing key
10
- const DEFAULT_SIGNATURE_ALGORITHM = "HS512"
11
-
12
- // Set default algorithm for auto-generated symmetric encryption key
13
- const DEFAULT_ENCRYPTION_ALGORITHM = "A256GCM"
14
-
15
- // Use encryption or not by default
16
- const DEFAULT_ENCRYPTION_ENABLED = false
17
-
18
9
const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days
19
10
11
+ const now = ( ) => ( Date . now ( ) / 1000 ) | 0
12
+
13
+ /** Issues a JWT. By default, the JWT is encrypted using "A256GCM". */
20
14
export async function encode ( {
21
15
token = { } ,
22
- maxAge = DEFAULT_MAX_AGE ,
23
16
secret,
24
- signingKey,
25
- signingOptions = {
26
- expiresIn : `${ maxAge } s` ,
27
- } ,
28
- encryptionKey,
29
- encryptionOptions = {
30
- alg : "dir" ,
31
- enc : DEFAULT_ENCRYPTION_ALGORITHM ,
32
- zip : "DEF" ,
33
- } ,
34
- encryption = DEFAULT_ENCRYPTION_ENABLED ,
17
+ maxAge = DEFAULT_MAX_AGE ,
35
18
} : JWTEncodeParams ) {
36
- // Signing Key
37
- const _signingKey = signingKey
38
- ? jose . JWK . asKey ( JSON . parse ( signingKey ) )
39
- : getDerivedSigningKey ( secret )
40
-
41
- // Sign token
42
- const signedToken = jose . JWT . sign ( token , _signingKey , signingOptions )
43
-
44
- if ( encryption ) {
45
- // Encryption Key
46
- const _encryptionKey = encryptionKey
47
- ? jose . JWK . asKey ( JSON . parse ( encryptionKey ) )
48
- : getDerivedEncryptionKey ( secret )
49
-
50
- // Encrypt token
51
- return jose . JWE . encrypt ( signedToken , _encryptionKey , encryptionOptions )
52
- }
53
- return signedToken
19
+ const encryptionSecret = await getDerivedEncryptionKey ( secret )
20
+ return await new EncryptJWT ( token )
21
+ . setProtectedHeader ( { alg : "dir" , enc : "A256GCM" } )
22
+ . setIssuedAt ( )
23
+ . setExpirationTime ( now ( ) + maxAge )
24
+ . setJti ( crypto . randomUUID ? crypto . randomUUID ( ) : uuid ( ) )
25
+ . encrypt ( encryptionSecret )
54
26
}
55
27
28
+ /** Decodes a NextAuth.js issued JWT. */
56
29
export async function decode ( {
57
- secret,
58
30
token,
59
- maxAge = DEFAULT_MAX_AGE ,
60
- signingKey,
61
- verificationKey = signingKey , // Optional (defaults to encryptionKey)
62
- verificationOptions = {
63
- maxTokenAge : `${ maxAge } s` ,
64
- algorithms : [ DEFAULT_SIGNATURE_ALGORITHM ] ,
65
- } ,
66
- encryptionKey,
67
- decryptionKey = encryptionKey , // Optional (defaults to encryptionKey)
68
- decryptionOptions = {
69
- algorithms : [ DEFAULT_ENCRYPTION_ALGORITHM ] ,
70
- } ,
71
- encryption = DEFAULT_ENCRYPTION_ENABLED ,
31
+ secret,
72
32
} : JWTDecodeParams ) : Promise < JWT | null > {
73
33
if ( ! token ) return null
74
-
75
- let tokenToVerify = token
76
-
77
- if ( encryption ) {
78
- // Encryption Key
79
- const _encryptionKey = decryptionKey
80
- ? jose . JWK . asKey ( JSON . parse ( decryptionKey ) )
81
- : getDerivedEncryptionKey ( secret )
82
-
83
- // Decrypt token
84
- const decryptedToken = jose . JWE . decrypt (
85
- token ,
86
- _encryptionKey ,
87
- decryptionOptions
88
- )
89
- tokenToVerify = decryptedToken . toString ( "utf8" )
90
- }
91
-
92
- // Signing Key
93
- const _signingKey = verificationKey
94
- ? jose . JWK . asKey ( JSON . parse ( verificationKey ) )
95
- : getDerivedSigningKey ( secret )
96
-
97
- // Verify token
98
- return jose . JWT . verify (
99
- tokenToVerify ,
100
- _signingKey ,
101
- verificationOptions
102
- ) as JWT | null
34
+ const encryptionSecret = await getDerivedEncryptionKey ( secret )
35
+ const { payload } = await jwtDecrypt ( token , encryptionSecret , {
36
+ clockTolerance : 15 ,
37
+ } )
38
+ return payload
103
39
}
104
40
105
41
export type GetTokenParams < R extends boolean = false > = {
42
+ /** The request containing the JWT either in the cookies or in the `Authorization` header. */
106
43
req : NextApiRequest
44
+ /**
45
+ * Use secure prefix for cookie name, unless URL in `NEXTAUTH_URL` is http://
46
+ * or not set (e.g. development or test instance) case use unprefixed name
47
+ */
107
48
secureCookie ?: boolean
49
+ /** If the JWT is in the cookie, what name `getToken()` should look for. */
108
50
cookieName ?: string
51
+ /**
52
+ * `getToken()` will return the raw JWT if this is set to `true`
53
+ * @default false
54
+ */
109
55
raw ?: R
110
- decode ?: typeof decode
111
- secret ?: string
112
- } & Omit < JWTDecodeParams , "secret" >
56
+ } & Pick < JWTOptions , "decode" | "secret" >
113
57
114
- /** [Documentation](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken) */
58
+ /**
59
+ * Takes a NextAuth.js request (`req`) and returns either the NextAuth.js issued JWT's payload,
60
+ * or the raw JWT string. We look for the JWT in the either the cookies, or the `Authorization` header.
61
+ * [Documentation](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken)
62
+ */
115
63
export async function getToken < R extends boolean = false > (
116
64
params ?: GetTokenParams < R >
117
65
) : Promise < R extends true ? string : JWT | null > {
118
66
const {
119
67
req,
120
- // Use secure prefix for cookie name, unless URL is NEXTAUTH_URL is http://
121
- // or not set (e.g. development or test instance) case use unprefixed name
122
68
secureCookie = ! (
123
69
! process . env . NEXTAUTH_URL ||
124
70
process . env . NEXTAUTH_URL . startsWith ( "http://" )
125
71
) ,
126
72
cookieName = secureCookie
127
73
? "__Secure-next-auth.session-token"
128
74
: "next-auth.session-token" ,
129
- raw = false ,
75
+ raw,
130
76
decode : _decode = decode ,
131
77
} = params ?? { }
78
+
132
79
if ( ! req ) throw new Error ( "Must pass `req` to JWT getToken()" )
133
80
134
- // Try to get token from cookie
135
81
let token = req . cookies [ cookieName ]
136
82
137
- // If cookie not found in cookie look for bearer token in authorization header.
138
- // This allows clients that pass through tokens in headers rather than as
139
- // cookies to use this helper function.
140
83
if ( ! token && req . headers . authorization ?. split ( " " ) [ 0 ] === "Bearer" ) {
141
84
const urlEncodedToken = req . headers . authorization . split ( " " ) [ 1 ]
142
85
token = decodeURIComponent ( urlEncodedToken )
@@ -156,22 +99,22 @@ export async function getToken<R extends boolean = false>(
156
99
}
157
100
}
158
101
159
- // Generate warning (but only once at startup) when auto-generated keys are used
160
- let DERIVED_SIGNING_KEY_WARNING = false
161
- let DERIVED_ENCRYPTION_KEY_WARNING = false
162
-
163
- // Do the better hkdf of Node.js one added in `v15.0.0` and Third Party one
164
- function hkdf ( secret , { byteLength, encryptionInfo, digest = "sha256" } ) {
165
- if ( crypto . hkdfSync ) {
166
- return Buffer . from (
167
- crypto . hkdfSync (
102
+ /** Do the better hkdf of Node.js one added in `v15.0.0` and Third Party one */
103
+ async function hkdf ( secret , { byteLength, encryptionInfo, digest = "sha256" } ) {
104
+ if ( crypto . hkdf ) {
105
+ return await new Promise ( ( resolve , reject ) => {
106
+ crypto . hkdf (
168
107
digest ,
169
108
secret ,
170
109
Buffer . alloc ( 0 ) ,
171
110
encryptionInfo ,
172
- byteLength
111
+ byteLength ,
112
+ ( err , derivedKey ) => {
113
+ if ( err ) reject ( err )
114
+ else resolve ( Buffer . from ( derivedKey ) )
115
+ }
173
116
)
174
- )
117
+ } )
175
118
}
176
119
// eslint-disable-next-line @typescript-eslint/no-var-requires
177
120
return require ( "futoin-hkdf" ) ( secret , byteLength , {
@@ -180,38 +123,9 @@ function hkdf(secret, { byteLength, encryptionInfo, digest = "sha256" }) {
180
123
} )
181
124
}
182
125
183
- function getDerivedSigningKey ( secret ) {
184
- if ( ! DERIVED_SIGNING_KEY_WARNING ) {
185
- logger . warn ( "JWT_AUTO_GENERATED_SIGNING_KEY" )
186
- DERIVED_SIGNING_KEY_WARNING = true
187
- }
188
-
189
- const buffer = hkdf ( secret , {
190
- byteLength : 64 ,
191
- encryptionInfo : "NextAuth.js Generated Signing Key" ,
192
- } )
193
- const key = jose . JWK . asKey ( buffer , {
194
- alg : DEFAULT_SIGNATURE_ALGORITHM ,
195
- use : "sig" ,
196
- kid : "nextauth-auto-generated-signing-key" ,
197
- } )
198
- return key
199
- }
200
-
201
- function getDerivedEncryptionKey ( secret ) {
202
- if ( ! DERIVED_ENCRYPTION_KEY_WARNING ) {
203
- logger . warn ( "JWT_AUTO_GENERATED_ENCRYPTION_KEY" )
204
- DERIVED_ENCRYPTION_KEY_WARNING = true
205
- }
206
-
207
- const buffer = hkdf ( secret , {
126
+ async function getDerivedEncryptionKey ( secret ) {
127
+ return await hkdf ( secret , {
208
128
byteLength : 32 ,
209
129
encryptionInfo : "NextAuth.js Generated Encryption Key" ,
210
130
} )
211
- const key = jose . JWK . asKey ( buffer , {
212
- alg : DEFAULT_ENCRYPTION_ALGORITHM ,
213
- use : "enc" ,
214
- kid : "nextauth-auto-generated-encryption-key" ,
215
- } )
216
- return key
217
131
}
0 commit comments