Skip to content

Commit

Permalink
fix: differentiate between issued JWTs
Browse files Browse the repository at this point in the history
  • Loading branch information
balazsorban44 committed Nov 10, 2023
1 parent 0f0c444 commit d237059
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 17 deletions.
32 changes: 25 additions & 7 deletions packages/next-auth/src/core/lib/oauth/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ export async function signCookie(

logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge })

const { name } = cookies[type]
const expires = new Date()
expires.setTime(expires.getTime() + maxAge * 1000)
return {
name: cookies[type].name,
value: await jwt.encode({ ...options.jwt, maxAge, token: { value } }),
name,
value: await jwt.encode({
...options.jwt,
maxAge,
token: { value },
salt: name,
}),
options: { ...cookies[type].options, expires },
}
}
Expand Down Expand Up @@ -71,16 +77,18 @@ export const pkce = {
if (!codeVerifier)
throw new TypeError("PKCE code_verifier cookie was missing.")

const { name } = options.cookies.pkceCodeVerifier
const value = (await jwt.decode({
...options.jwt,
token: codeVerifier,
salt: name,
})) as any

if (!value?.value)
throw new TypeError("PKCE code_verifier value could not be parsed.")

resCookies.push({
name: options.cookies.pkceCodeVerifier.name,
name,
value: "",
options: { ...options.cookies.pkceCodeVerifier.options, maxAge: 0 },
})
Expand Down Expand Up @@ -121,12 +129,17 @@ export const state = {

if (!state) throw new TypeError("State cookie was missing.")

const value = (await jwt.decode({ ...options.jwt, token: state })) as any
const { name } = options.cookies.state
const value = (await jwt.decode({
...options.jwt,
token: state,
salt: name,
})) as any

if (!value?.value) throw new TypeError("State value could not be parsed.")

resCookies.push({
name: options.cookies.state.name,
name,
value: "",
options: { ...options.cookies.state.options, maxAge: 0 },
})
Expand Down Expand Up @@ -166,12 +179,17 @@ export const nonce = {
const nonce = cookies?.[options.cookies.nonce.name]
if (!nonce) throw new TypeError("Nonce cookie was missing.")

const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any
const { name } = options.cookies.nonce
const value = (await jwt.decode({
...options.jwt,
token: nonce,
salt: name,
})) as any

if (!value?.value) throw new TypeError("Nonce value could not be parsed.")

resCookies.push({
name: options.cookies.nonce.name,
name,
value: "",
options: { ...options.cookies.nonce.options, maxAge: 0 },
})
Expand Down
21 changes: 13 additions & 8 deletions packages/next-auth/src/jwt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ const now = () => (Date.now() / 1000) | 0

/** Issues a JWT. By default, the JWT is encrypted using "A256GCM". */
export async function encode(params: JWTEncodeParams) {
const { token = {}, secret, maxAge = DEFAULT_MAX_AGE } = params
const encryptionSecret = await getDerivedEncryptionKey(secret)
/** @note empty `salt` means a session token. See {@link JWTEncodeParams.salt}. */
const { token = {}, secret, maxAge = DEFAULT_MAX_AGE, salt = "" } = params
const encryptionSecret = await getDerivedEncryptionKey(secret, salt)
return await new EncryptJWT(token)
.setProtectedHeader({ alg: "dir", enc: "A256GCM" })
.setIssuedAt()
Expand All @@ -27,9 +28,10 @@ export async function encode(params: JWTEncodeParams) {

/** Decodes a NextAuth.js issued JWT. */
export async function decode(params: JWTDecodeParams): Promise<JWT | null> {
const { token, secret } = params
/** @note empty `salt` means a session token. See {@link JWTDecodeParams.salt}. */
const { token, secret, salt = "" } = params
if (!token) return null
const encryptionSecret = await getDerivedEncryptionKey(secret)
const encryptionSecret = await getDerivedEncryptionKey(secret, salt)
const { payload } = await jwtDecrypt(token, encryptionSecret, {
clockTolerance: 15,
})
Expand Down Expand Up @@ -116,12 +118,15 @@ export async function getToken<R extends boolean = false>(
}
}

async function getDerivedEncryptionKey(secret: string | Buffer) {
async function getDerivedEncryptionKey(
keyMaterial: string | Buffer,
salt: string
) {
return await hkdf(
"sha256",
secret,
"",
"NextAuth.js Generated Encryption Key",
keyMaterial,
salt,
`NextAuth.js Generated Encryption Key${salt ? ` (${salt})` : ""}`,
32
)
}
16 changes: 14 additions & 2 deletions packages/next-auth/src/jwt/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ export interface JWT extends Record<string, unknown>, DefaultJWT {}
export interface JWTEncodeParams {
/** The JWT payload. */
token?: JWT
/** The secret used to encode the NextAuth.js issued JWT. */
/**
* Used in combination with `secret` when deriving the encryption secret for the various NextAuth.js-issued JWTs.
* @note When no `salt` is passed, we assume this is a session token.
* This is for backwards-compatibility with currently active sessions, so they won't be invalidated when upgrading the package.
*/
salt?: string
/** The key material used to encode the NextAuth.js issued JWTs. Defaults to `NEXTAUTH_SECRET`. */
secret: string | Buffer
/**
* The maximum age of the NextAuth.js issued JWT in seconds.
Expand All @@ -29,7 +35,13 @@ export interface JWTEncodeParams {
export interface JWTDecodeParams {
/** The NextAuth.js issued JWT to be decoded */
token?: string
/** The secret used to decode the NextAuth.js issued JWT. */
/**
* Used in combination with `secret` when deriving the encryption secret for the various NextAuth.js-issued JWTs.
* @note When no `salt` is passed, we assume this is a session token.
* This is for backwards-compatibility with currently active sessions, so they won't be invalidated when upgrading the package.
*/
salt?: string
/** The key material used to decode the NextAuth.js issued JWTs. Defaults to `NEXTAUTH_SECRET`. */
secret: string | Buffer
}

Expand Down

0 comments on commit d237059

Please sign in to comment.