Medium severityNVD Advisory· Published Apr 29, 2025· Updated Apr 15, 2026
CVE-2025-46344
CVE-2025-46344
Description
The Auth0 Next.js SDK is a library for implementing user authentication in Next.js applications. Versions starting from 4.0.1 and prior to 4.5.1, do not invoke .setExpirationTime when generating a JWE token for the session. As a result, the JWE does not contain an internal expiration claim. While the session cookie may expire or be cleared, the JWE remains valid. This issue has been patched in version 4.5.1.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@auth0/nextjs-auth0npm | >= 4.0.1, < 4.5.1 | 4.5.1 |
Patches
2a4f061aed02ffix: Ensure JWE expires as expected (#2040)
11 files changed · +195 −116
src/server/auth-client.test.ts+85 −46 modified@@ -496,7 +496,9 @@ ca/T0LLtgmbMmxSv/MmzIg== createdAt: Math.floor(Date.now() / 1000) } }; - const sessionCookie = await encrypt(session, secret); + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const sessionCookie = await encrypt(session, secret, expiration); const headers = new Headers(); headers.append("cookie", `__session=${sessionCookie}`); const request = new NextRequest( @@ -516,7 +518,7 @@ ca/T0LLtgmbMmxSv/MmzIg== updatedSessionCookie!.value, secret ); - expect(updatedSessionCookieValue).toEqual({ + expect(updatedSessionCookieValue).toEqual(expect.objectContaining({ user: { sub: DEFAULT.sub }, @@ -529,7 +531,7 @@ ca/T0LLtgmbMmxSv/MmzIg== sid: DEFAULT.sid, createdAt: expect.any(Number) } - }); + })); // assert that the session expiry has been extended by the inactivity duration expect(updatedSessionCookie?.maxAge).toEqual(1800); @@ -868,13 +870,13 @@ ca/T0LLtgmbMmxSv/MmzIg== ); expect(transactionCookie).toBeDefined(); expect((await decrypt(transactionCookie!.value, secret)).payload).toEqual( - { + expect.objectContaining({ nonce: authorizationUrl.searchParams.get("nonce"), codeVerifier: expect.any(String), responseType: "code", state: authorizationUrl.searchParams.get("state"), returnTo: "/" - } + }) ); }); @@ -1023,13 +1025,13 @@ ca/T0LLtgmbMmxSv/MmzIg== expect(transactionCookie).toBeDefined(); expect( (await decrypt(transactionCookie!.value, secret)).payload - ).toEqual({ + ).toEqual(expect.objectContaining({ nonce: authorizationUrl.searchParams.get("nonce"), codeVerifier: expect.any(String), responseType: "code", state: authorizationUrl.searchParams.get("state"), returnTo: "/" - }); + })); }); it("should forward the configured authorization parameters to the authorization server", async () => { @@ -1356,14 +1358,14 @@ ca/T0LLtgmbMmxSv/MmzIg== ); expect(transactionCookie).toBeDefined(); expect((await decrypt(transactionCookie!.value, secret)).payload).toEqual( - { + expect.objectContaining({ nonce: authorizationUrl.searchParams.get("nonce"), maxAge: 3600, codeVerifier: expect.any(String), responseType: "code", state: authorizationUrl.searchParams.get("state"), returnTo: "/" - } + }) ); }); @@ -1403,13 +1405,13 @@ ca/T0LLtgmbMmxSv/MmzIg== ); expect(transactionCookie).toBeDefined(); expect((await decrypt(transactionCookie!.value, secret)).payload).toEqual( - { + expect.objectContaining({ nonce: authorizationUrl.searchParams.get("nonce"), codeVerifier: expect.any(String), responseType: "code", state: authorizationUrl.searchParams.get("state"), returnTo: "https://example.com/dashboard" - } + }) ); }); @@ -1449,13 +1451,13 @@ ca/T0LLtgmbMmxSv/MmzIg== ); expect(transactionCookie).toBeDefined(); expect((await decrypt(transactionCookie!.value, secret)).payload).toEqual( - { + expect.objectContaining({ nonce: authorizationUrl.searchParams.get("nonce"), codeVerifier: expect.any(String), responseType: "code", state: authorizationUrl.searchParams.get("state"), returnTo: "/" - } + }) ); }); @@ -1583,13 +1585,13 @@ ca/T0LLtgmbMmxSv/MmzIg== expect(transactionCookie).toBeDefined(); expect( (await decrypt(transactionCookie!.value, secret)).payload - ).toEqual({ + ).toEqual(expect.objectContaining({ nonce: expect.any(String), codeVerifier: expect.any(String), responseType: "code", state, returnTo: "/" - }); + })); }); describe("custom parameters to the authorization server", async () => { @@ -1662,13 +1664,13 @@ ca/T0LLtgmbMmxSv/MmzIg== expect(transactionCookie).toBeDefined(); expect( (await decrypt(transactionCookie!.value, secret)).payload - ).toEqual({ + ).toEqual(expect.objectContaining({ nonce: expect.any(String), codeVerifier: expect.any(String), responseType: "code", state, returnTo: "/" - }); + })); }); it("should forward custom parameters set in the configuration to the authorization server", async () => { @@ -1742,13 +1744,13 @@ ca/T0LLtgmbMmxSv/MmzIg== expect(transactionCookie).toBeDefined(); expect( (await decrypt(transactionCookie!.value, secret)).payload - ).toEqual({ + ).toEqual(expect.objectContaining({ nonce: expect.any(String), codeVerifier: expect.any(String), responseType: "code", state, returnTo: "/" - }); + })); }); }); }); @@ -1838,7 +1840,9 @@ ca/T0LLtgmbMmxSv/MmzIg== createdAt: Math.floor(Date.now() / 1000) } }; - const sessionCookie = await encrypt(session, secret); + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const sessionCookie = await encrypt(session, secret, expiration); const headers = new Headers(); headers.append("cookie", `__session=${sessionCookie}`); const request = new NextRequest( @@ -1911,7 +1915,9 @@ ca/T0LLtgmbMmxSv/MmzIg== createdAt: Math.floor(Date.now() / 1000) } }; - const sessionCookie = await encrypt(session, secret); + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const sessionCookie = await encrypt(session, secret, expiration); const headers = new Headers(); headers.append("cookie", `__session=${sessionCookie}`); @@ -2173,7 +2179,9 @@ ca/T0LLtgmbMmxSv/MmzIg== createdAt: Math.floor(Date.now() / 1000) } }; - const sessionCookie = await encrypt(session, secret); + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const sessionCookie = await encrypt(session, secret, expiration); const headers = new Headers(); headers.append("cookie", `__session=${sessionCookie}`); const request = new NextRequest( @@ -2268,9 +2276,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -2288,7 +2298,7 @@ ca/T0LLtgmbMmxSv/MmzIg== const sessionCookie = response.cookies.get("__session"); expect(sessionCookie).toBeDefined(); const { payload: session } = await decrypt(sessionCookie!.value, secret); - expect(session).toEqual({ + expect(session).toEqual(expect.objectContaining({ user: { sub: DEFAULT.sub }, @@ -2302,7 +2312,7 @@ ca/T0LLtgmbMmxSv/MmzIg== sid: expect.any(String), createdAt: expect.any(Number) } - }); + })); // validate the transaction cookie has been removed const transactionCookie = response.cookies.get(`__txn_${state}`); @@ -2377,9 +2387,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -2397,7 +2409,7 @@ ca/T0LLtgmbMmxSv/MmzIg== const sessionCookie = response.cookies.get("__session"); expect(sessionCookie).toBeDefined(); const { payload: session } = await decrypt(sessionCookie!.value, secret); - expect(session).toEqual({ + expect(session).toEqual(expect.objectContaining({ user: { sub: DEFAULT.sub }, @@ -2411,7 +2423,7 @@ ca/T0LLtgmbMmxSv/MmzIg== sid: expect.any(String), createdAt: expect.any(Number) } - }); + })); // validate the transaction cookie has been removed const transactionCookie = response.cookies.get(`__txn_${state}`); @@ -2496,9 +2508,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_does-not-exist=${await encrypt(transactionState, secret)}` + `__txn_does-not-exist=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -2548,9 +2562,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -2607,9 +2623,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -2663,9 +2681,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -2726,9 +2746,12 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); + headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -2773,7 +2796,7 @@ ca/T0LLtgmbMmxSv/MmzIg== sessionCookie!.value, secret ); - expect(session).toEqual(expectedSession); + expect(session).toEqual(expect.objectContaining(expectedSession)); }); it("should be called with an error if the state parameter is missing", async () => { @@ -2881,9 +2904,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_non-existent-state=${await encrypt(transactionState, secret)}` + `__txn_non-existent-state=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -2956,9 +2981,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -3040,9 +3067,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -3124,9 +3153,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -3204,9 +3235,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -3227,7 +3260,7 @@ ca/T0LLtgmbMmxSv/MmzIg== sessionCookie!.value, secret ); - expect(session).toEqual({ + expect(session).toEqual(expect.objectContaining({ user: { sub: DEFAULT.sub, name: "John Doe", @@ -3244,7 +3277,7 @@ ca/T0LLtgmbMmxSv/MmzIg== sid: expect.any(String), createdAt: expect.any(Number) } - }); + })); }); it("should not call the hook if the session is not established", async () => { @@ -3334,9 +3367,11 @@ ca/T0LLtgmbMmxSv/MmzIg== state: state, returnTo: "/dashboard" }; + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); headers.set( "cookie", - `__txn_${state}=${await encrypt(transactionState, secret)}` + `__txn_${state}=${await encrypt(transactionState, secret, expiration)}` ); const request = new NextRequest(url, { method: "GET", @@ -3357,7 +3392,7 @@ ca/T0LLtgmbMmxSv/MmzIg== sessionCookie!.value, secret ); - expect(session).toEqual({ + expect(session).toEqual(expect.objectContaining({ user: { sub: DEFAULT.sub, name: "John Doe", @@ -3374,7 +3409,7 @@ ca/T0LLtgmbMmxSv/MmzIg== sid: expect.any(String), createdAt: expect.any(Number) } - }); + })); }); }); }); @@ -3432,7 +3467,9 @@ ca/T0LLtgmbMmxSv/MmzIg== createdAt: Math.floor(Date.now() / 1000) } }; - const sessionCookie = await encrypt(session, secret); + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const sessionCookie = await encrypt(session, secret, expiration); const headers = new Headers(); headers.append("cookie", `__session=${sessionCookie}`); const request = new NextRequest( @@ -3543,7 +3580,9 @@ ca/T0LLtgmbMmxSv/MmzIg== createdAt: Math.floor(Date.now() / 1000) } }; - const sessionCookie = await encrypt(session, secret); + const maxAge = 60 * 60; // 1 hour + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const sessionCookie = await encrypt(session, secret, expiration); const headers = new Headers(); headers.append("cookie", `__session=${sessionCookie}`); const request = new NextRequest(
src/server/cookies.test.ts+24 −5 modified@@ -9,28 +9,47 @@ describe("encrypt/decrypt", async () => { it("should encrypt/decrypt a payload with the correct secret", async () => { const payload = { key: "value" }; - const encrypted = await encrypt(payload, secret); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const encrypted = await encrypt(payload, secret, expiration); const decrypted = await decrypt(encrypted, secret); - expect(decrypted.payload).toEqual(payload); + expect(decrypted.payload).toEqual(expect.objectContaining(payload)); }); it("should fail to decrypt a payload with the incorrect secret", async () => { const payload = { key: "value" }; - const encrypted = await encrypt(payload, secret); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const encrypted = await encrypt(payload, secret, expiration); await expect(() => decrypt(encrypted, incorrectSecret) ).rejects.toThrowError(); }); + it("should fail to decrypt when expired", async () => { + const payload = { key: "value" }; + const expiration = Math.floor(Date.now() / 1000 - 60); // 60 seconds in the past + const encrypted = await encrypt(payload, secret, expiration); + await expect(() => + decrypt(encrypted, secret) + ).rejects.toThrowError(`"exp" claim timestamp check failed`); + }); + it("should fail to encrypt if a secret is not provided", async () => { const payload = { key: "value" }; - await expect(() => encrypt(payload, "")).rejects.toThrowError(); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + + await expect(() => encrypt(payload, "", expiration)).rejects.toThrowError(); }); it("should fail to decrypt if a secret is not provided", async () => { const payload = { key: "value" }; - const encrypted = await encrypt(payload, secret); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + + const encrypted = await encrypt(payload, secret, expiration); await expect(() => decrypt(encrypted, "")).rejects.toThrowError(); }); });
src/server/cookies.ts+2 −0 modified@@ -15,6 +15,7 @@ const ENCRYPTION_INFO = "JWE CEK"; export async function encrypt( payload: jose.JWTPayload, secret: string, + expiration: number, additionalHeaders?: { iat: number; uat: number; @@ -31,6 +32,7 @@ export async function encrypt( const encryptedCookie = await new jose.EncryptJWT(payload) .setProtectedHeader({ enc: ENC, alg: ALG, ...additionalHeaders }) + .setExpirationTime(expiration) .encrypt(encryptionSecret); return encryptedCookie.toString();
src/server/session/stateful-session-store.test.ts+16 −5 modified@@ -34,11 +34,14 @@ describe("Stateful Session Store", async () => { set: vi.fn(), delete: vi.fn() }; + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); const encryptedCookieValue = await encrypt( { id: sessionId }, - secret + secret, + expiration, ); const headers = new Headers(); @@ -99,11 +102,14 @@ describe("Stateful Session Store", async () => { set: vi.fn(), delete: vi.fn() }; + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); const encryptedCookieValue = await encrypt( { id: sessionId }, - secret + secret, + expiration, ); const headers = new Headers(); @@ -464,12 +470,14 @@ describe("Stateful Session Store", async () => { set: vi.fn(), delete: vi.fn() }; - + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); const encryptedCookieValue = await encrypt( { id: sessionId }, - secret + secret, + expiration, ); const headers = new Headers(); headers.append("cookie", `__session=${encryptedCookieValue}`); @@ -750,11 +758,14 @@ describe("Stateful Session Store", async () => { set: vi.fn(), delete: vi.fn() }; + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); const encryptedCookieValue = await encrypt( { id: sessionId }, - secret + secret, + expiration, ); const headers = new Headers(); headers.append("cookie", `__session=${encryptedCookieValue}`);
src/server/session/stateful-session-store.ts+6 −4 modified@@ -130,15 +130,17 @@ export class StatefulSessionStore extends AbstractSessionStore { if (!sessionId) { sessionId = generateId(); } - + + const maxAge = this.calculateMaxAge(session.internal.createdAt); + const expiration = Date.now() / 1000 + maxAge; const jwe = await cookies.encrypt( { id: sessionId }, - this.secret + this.secret, + expiration, ); - const maxAge = this.calculateMaxAge(session.internal.createdAt); - + resCookies.set(this.sessionCookieName, jwe.toString(), { ...this.cookieConfig, maxAge
src/server/session/stateless-session-store.test.ts+27 −16 modified@@ -22,7 +22,9 @@ describe("Stateless Session Store", async () => { createdAt: Math.floor(Date.now() / 1000) } }; - const encryptedCookieValue = await encrypt(session, secret); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const encryptedCookieValue = await encrypt(session, secret, expiration); const headers = new Headers(); headers.append("cookie", `__session=${encryptedCookieValue}`); @@ -32,7 +34,7 @@ describe("Stateless Session Store", async () => { secret }); - expect(await sessionStore.get(requestCookies)).toEqual(session); + expect(await sessionStore.get(requestCookies)).toEqual(expect.objectContaining(session)); }); it("should return null if no session cookie exists", async () => { @@ -65,9 +67,12 @@ describe("Stateless Session Store", async () => { uat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) }; + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); const encryptedCookieValue = await encrypt( legacySession, secret, + expiration, legacyHeader ); @@ -106,9 +111,12 @@ describe("Stateless Session Store", async () => { uat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) }; + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); const encryptedCookieValue = await encrypt( legacySession, secret, + expiration, legacyHeader ); @@ -153,9 +161,12 @@ describe("Stateless Session Store", async () => { uat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) }; + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); const encryptedCookieValue = await encrypt( legacySession, secret, + expiration, legacyHeader ); @@ -206,7 +217,9 @@ describe("Stateless Session Store", async () => { } ] }; - const encryptedCookieValue = await encrypt(session, secret); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const encryptedCookieValue = await encrypt(session, secret, expiration); const headers = new Headers(); headers.append("cookie", `__session=${encryptedCookieValue}`); @@ -216,7 +229,7 @@ describe("Stateless Session Store", async () => { secret }); - expect(await sessionStore.get(requestCookies)).toEqual(session); + expect(await sessionStore.get(requestCookies)).toEqual(expect.objectContaining(session)); }); }); @@ -264,7 +277,7 @@ describe("Stateless Session Store", async () => { const cookie = responseCookies.get("__session"); expect(cookie).toBeDefined(); - expect((await decrypt(cookie!.value, secret)).payload).toEqual(session); + expect((await decrypt(cookie!.value, secret)).payload).toEqual(expect.objectContaining(session)); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); expect(cookie?.sameSite).toEqual("lax"); @@ -306,12 +319,10 @@ describe("Stateless Session Store", async () => { const cookie = responseCookies.get("__session"); expect(cookie).toBeDefined(); - expect((await decrypt(cookie!.value, secret)).payload).toEqual(session); - expect(cookie?.path).toEqual("/"); - expect(cookie?.httpOnly).toEqual(true); - expect(cookie?.sameSite).toEqual("lax"); - expect(cookie?.maxAge).toEqual(0); // cookie should expire immediately - expect(cookie?.secure).toEqual(false); + + await expect( + decrypt(cookie!.value, secret) + ).rejects.toThrow(`"exp" claim timestamp check failed`); }); it("should delete the legacy cookie if it exists", async () => { @@ -413,7 +424,7 @@ describe("Stateless Session Store", async () => { const cookie = responseCookies.get("__session"); expect(cookie).toBeDefined(); - expect((await decrypt(cookie!.value, secret)).payload).toEqual(session); + expect((await decrypt(cookie!.value, secret)).payload).toEqual(expect.objectContaining(session)); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); expect(cookie?.sameSite).toEqual("lax"); @@ -453,7 +464,7 @@ describe("Stateless Session Store", async () => { const cookie = responseCookies.get("__session"); expect(cookie).toBeDefined(); - expect((await decrypt(cookie!.value, secret)).payload).toEqual(session); + expect((await decrypt(cookie!.value, secret)).payload).toEqual(expect.objectContaining(session)); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); expect(cookie?.sameSite).toEqual("lax"); @@ -492,7 +503,7 @@ describe("Stateless Session Store", async () => { const cookie = responseCookies.get("__session"); expect(cookie).toBeDefined(); - expect((await decrypt(cookie!.value, secret)).payload).toEqual(session); + expect((await decrypt(cookie!.value, secret)).payload).toEqual(expect.objectContaining(session)); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); expect(cookie?.sameSite).toEqual("strict"); @@ -528,7 +539,7 @@ describe("Stateless Session Store", async () => { const cookie = responseCookies.get("__session"); expect(cookie).toBeDefined(); - expect((await decrypt(cookie!.value, secret)).payload).toEqual(session); + expect((await decrypt(cookie!.value, secret)).payload).toEqual(expect.objectContaining(session)); expect(cookie?.path).toEqual("/custom-path"); }); @@ -563,7 +574,7 @@ describe("Stateless Session Store", async () => { const cookie = responseCookies.get("custom-session"); expect(cookie).toBeDefined(); - expect((await decrypt(cookie!.value, secret)).payload).toEqual(session); + expect((await decrypt(cookie!.value, secret)).payload).toEqual(expect.objectContaining(session)); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); expect(cookie?.sameSite).toEqual("lax");
src/server/session/stateless-session-store.ts+4 −2 modified@@ -87,8 +87,9 @@ export class StatelessSessionStore extends AbstractSessionStore { session: SessionData ) { const { connectionTokenSets, ...originalSession } = session; - const jwe = await cookies.encrypt(originalSession, this.secret); const maxAge = this.calculateMaxAge(session.internal.createdAt); + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const jwe = await cookies.encrypt(originalSession, this.secret, expiration); const cookieValue = jwe.toString(); const options: CookieOptions = { ...this.cookieConfig, @@ -141,7 +142,8 @@ export class StatelessSessionStore extends AbstractSessionStore { cookieName: string, maxAge: number ) { - const jwe = await cookies.encrypt(session, this.secret); + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const jwe = await cookies.encrypt(session, this.secret, expiration); const cookieValue = jwe.toString();
src/server/transaction-store.test.ts+13 −9 modified@@ -20,7 +20,9 @@ describe("Transaction Store", async () => { state, returnTo: "/dashboard" }; - const encryptedCookieValue = await encrypt(transactionState, secret); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const encryptedCookieValue = await encrypt(transactionState, secret, expiration); const headers = new Headers(); headers.append("cookie", `__txn_${state}=${encryptedCookieValue}`); @@ -32,7 +34,7 @@ describe("Transaction Store", async () => { expect( (await transactionStore.get(requestCookies, state))?.payload - ).toEqual(transactionState); + ).toEqual(expect.objectContaining(transactionState)); }); it("should return null if no transaction cookie with a matching state exists", async () => { @@ -48,7 +50,9 @@ describe("Transaction Store", async () => { state, returnTo: "/dashboard" }; - const encryptedCookieValue = await encrypt(transactionState, secret); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + const encryptedCookieValue = await encrypt(transactionState, secret, expiration); const headers = new Headers(); headers.append("cookie", `__txn_incorrect-state=${encryptedCookieValue}`); @@ -89,7 +93,7 @@ describe("Transaction Store", async () => { expect(cookie).toBeDefined(); expect((await decrypt(cookie!.value, secret)).payload).toEqual( - transactionState + expect.objectContaining(transactionState) ); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); @@ -152,7 +156,7 @@ describe("Transaction Store", async () => { expect(cookie).toBeDefined(); expect((await decrypt(cookie!.value, secret)).payload).toEqual( - transactionState + expect.objectContaining(transactionState) ); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); @@ -190,7 +194,7 @@ describe("Transaction Store", async () => { expect(cookie).toBeDefined(); expect((await decrypt(cookie!.value, secret)).payload).toEqual( - transactionState + expect.objectContaining(transactionState) ); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); @@ -228,7 +232,7 @@ describe("Transaction Store", async () => { expect(cookie).toBeDefined(); expect((await decrypt(cookie!.value, secret)).payload).toEqual( - transactionState + expect.objectContaining(transactionState) ); expect(cookie?.path).toEqual("/custom-path"); }); @@ -261,9 +265,9 @@ describe("Transaction Store", async () => { const cookie = responseCookies.get(cookieName); expect(cookie).toBeDefined(); - expect((await decrypt(cookie!.value, secret)).payload).toEqual( + expect((await decrypt(cookie!.value, secret)).payload).toEqual(expect.objectContaining( transactionState - ); + )); expect(cookie?.path).toEqual("/"); expect(cookie?.httpOnly).toEqual(true); expect(cookie?.sameSite).toEqual("lax");
src/server/transaction-store.ts+3 −1 modified@@ -79,7 +79,9 @@ export class TransactionStore { resCookies: cookies.ResponseCookies, transactionState: TransactionState ) { - const jwe = await cookies.encrypt(transactionState, this.secret); + + const expiration = Math.floor(Date.now() / 1000 + this.cookieConfig.maxAge!); + const jwe = await cookies.encrypt(transactionState, this.secret, expiration); if (!transactionState.state) { throw new Error("Transaction state is required");
src/testing/generate-session-cookie.test.ts+11 −27 modified@@ -29,7 +29,7 @@ describe("generateSessionCookie", async () => { }; const sessionCookie = await generateSessionCookie(session, config); expect(sessionCookie).toEqual(expect.any(String)); - expect((await decrypt(sessionCookie, secret)).payload).toEqual({ + expect((await decrypt(sessionCookie, secret)).payload).toEqual(expect.objectContaining({ user: { sub: "user_123" }, @@ -42,7 +42,7 @@ describe("generateSessionCookie", async () => { sid: "auth0-sid", createdAt: createdAt } - }); + })); }); it("should populate the internal property if it was not provided", async () => { @@ -60,7 +60,7 @@ describe("generateSessionCookie", async () => { }; const sessionCookie = await generateSessionCookie(session, config); expect(sessionCookie).toEqual(expect.any(String)); - expect((await decrypt(sessionCookie, secret)).payload).toEqual({ + expect((await decrypt(sessionCookie, secret)).payload).toEqual(expect.objectContaining({ user: { sub: "user_123" }, @@ -73,7 +73,7 @@ describe("generateSessionCookie", async () => { sid: "auth0-sid", createdAt: expect.any(Number) } - }); + })); }); it("should not populate the internal property if a null was provided", async () => { @@ -93,20 +93,12 @@ describe("generateSessionCookie", async () => { }; const sessionCookie = await generateSessionCookie(session, config); expect(sessionCookie).toEqual(expect.any(String)); - expect((await decrypt(sessionCookie, secret)).payload).toEqual({ - user: { - sub: "user_123" - }, - tokenSet: { - accessToken: "at_123", - refreshToken: "rt_123", - expiresAt: 123456 - }, - internal: null - }); + expect((await decrypt(sessionCookie, secret)).payload).not.toEqual(expect.objectContaining({ + internal: expect.anything() + })); }); - it("should not populate the internal property if a undefine was provided", async () => { + it("should not populate the internal property if a undefined was provided", async () => { const session: Partial<SessionData> = { user: { sub: "user_123" }, tokenSet: { @@ -122,16 +114,8 @@ describe("generateSessionCookie", async () => { }; const sessionCookie = await generateSessionCookie(session, config); expect(sessionCookie).toEqual(expect.any(String)); - expect((await decrypt(sessionCookie, secret)).payload).toEqual({ - user: { - sub: "user_123" - }, - tokenSet: { - accessToken: "at_123", - refreshToken: "rt_123", - expiresAt: 123456 - }, - internal: undefined - }); + expect((await decrypt(sessionCookie, secret)).payload).not.toEqual(expect.objectContaining({ + internal: expect.anything() + })); }); });
src/testing/generate-session-cookie.ts+4 −1 modified@@ -21,5 +21,8 @@ export const generateSessionCookie = async ( }; } - return encrypt(session, config.secret); + const maxAge = 60 * 60; // 1 hour in seconds + const expiration = Math.floor(Date.now() / 1000 + maxAge); + + return encrypt(session, config.secret, expiration); };
c44432c49468Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-pjr6-jx7r-j4r6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-46344ghsaADVISORY
- github.com/auth0/nextjs-auth0/commit/a4f061aed02ffa132feca8adfbd11704df17e1c3nvdWEB
- github.com/auth0/nextjs-auth0/releases/tag/v4.5.1nvdWEB
- github.com/auth0/nextjs-auth0/security/advisories/GHSA-pjr6-jx7r-j4r6nvdWEB
News mentions
0No linked articles in our index yet.