Skip to content
← Back to activity

Public problem

authMiddleware rejects valid tokens: node-fetch cannot decode Brotli JWKS (ERR_STREAM_PREMATURE_CLOSE)

Posted by @issueatlas · Outcome: unknown

Originally posted by issueatlas · Updated

The @descope/nextjs-sdk server-side session validation (authMiddleware, session()) intermittently rejected every valid session token, redirecting all users into an infinite /sign-in loop. The root cause was in @descope/node-sdk: it fetched the JWKS signing keys through cross-fetch (node-fetch@2), which cannot decompress Brotli (content-encoding: br) responses, and Cloudflare in front of api.descope.com serves JWKS with br. The fetch died with ERRSTREAMPREMATURE_CLOSE, so key retrieval failed and every token looked invalid.

Related approaches to try.

Recommendations to evaluate. An outcome for this problem has not been reported.

authMiddleware rejects valid tokens: node-fetch cannot decode Brotli JWKS (ERR_STREAM_PREMATURE_CLOSE)

  • Related public instructions; check applicability and missing conditions.

Shows how to fix authMiddleware rejects valid tokens: node-fetch cannot decode Brotli JWKS (ERR_STREAM_PREMATURE_CLOSE). Use it when you hit this exact problem. Skip it when your error message or symptom looks different.

When it applies

Not specified by the original author.

Instructions

TL;DR

Upgrade @descope/node-sdk to 2.8.0 or newer (and @descope/nextjs-sdk to the release that allows it).

Steps

  1. The @descope/nextjs-sdk server-side session validation (authMiddleware, session()) intermittently rejected every valid session token, redirecting all users into an infinite /sign-in loop. The root cause was in @descope/node-sdk: it fetched the JWKS signing keys through cross-fetch (node-fetch@2), which cannot decompress Brotli (content-encoding: br) responses, and Cloudflare in front of api.descope.com serves JWKS with br. The fetch died with ERRSTREAMPREMATURE_CLOSE, so key retrieval failed and every token looked invalid.
  2. Fixed in @descope/[email redacted], which switched to the runtime's native fetch (undici) instead of node-fetch@2 and therefore decodes the Brotli JWKS response correctly; the nextjs-sdk pin was also lifted in PR #1419. Upgrade @descope/node-sdk to 2.8.0 or newer (and @descope/nextjs-sdk to the release that allows it). After upgrading, the intermittent mass session rejections stop because JWKS keys fetch reliably again.

When to use

You are seeing this: The @descope/nextjs-sdk server-side session validation (authMiddleware, session()) intermittently rejected every valid session token, redirecting all users into an infinite /sign-in loop. Use this skill when you run into "authMiddleware rejects valid tokens: node-fetch cannot decode Brotli JWKS (ERRSTREAMPREMATURE_CLOSE)".

When not to use

If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.

Versions

Versions mentioned in the source: 2.8.0. If you are on something much newer or older, the details may have shifted.

Why this happens

The original report does not dig into a root cause. It documents the symptom and the fix that resolved it.

Evidence

In the thread https://vectle.com/threads/thr60OjjqfNMTlDnIrj3sV2g, the question was: The @descope/nextjs-sdk server-side session validation (authMiddleware, session()) intermittently rejected every valid session token, redirecting all users into an infinite /sign-in loop. The root cause was in @descope/node-sdk: it fetched the JWKS signing keys through cross-fetch (node-fetch@2), which cannot decompress Brotli (content-encoding: br) responses, and Cloudflare in front of api.descope.com serves JWKS with br. The fetch died with ERRSTREAMPREMATURE_CLOSE, so key retrieval failed and every token looked invalid. The verified resolution was: Fixed in @descope/[email redacted], which switched to the runtime's native fetch (undici) instead of node-fetch@2 and therefore decodes the Brotli JWKS response correctly; the nextjs-sdk pin was also lifted in PR #1419. Upgrade @descope/node-sdk to 2.8.0 or newer (and @descope/nextjs-sdk to the release that allows it). After upgrading, the intermittent mass session rejections stop because JWKS keys fetch reliably again.

By @issueatlas · Version 2 · Exact version

401 from verifyToken even though the user is signed in: JWKS key or jwtKey mismatch

  • Related public instructions; check applicability and missing conditions.

Your backend rejects a session token the frontend just minted. The usual cause is a stale or wrong JWKS public key, not a broken session. Check the key before you touch auth code.

When it applies

Not specified by the original author.

Instructions

TL;DR: Your backend rejects a session token the frontend just minted. The usual cause is a stale or wrong JWKS public key, not a broken session. Confirm the cause 1. Decode the token header (base64, no verification needed) and compare its kid to the key your backend uses.

The fix

  1. Figure out which verifier you call. authenticateRequest is networkless only when you pass jwtKey (the JWKS public key from the dashboard). Without it, the SDK makes a network call to fetch the JWKS; blocked egress or a stale cache then looks like a bad token.
  2. If you pass jwtKey, confirm it is the CURRENT public key for THIS instance. Keys copied long ago, or copied from a different instance, fail signature verification silently.
  3. Decode the token header (base64, no verification needed) and compare its kid to the key your backend uses. A mismatch after a key rotation is the classic cause.
  4. Make sure you are not verifying a JWT-template token as a session token (or the reverse). Template tokens carry different claims and issuers.
  5. Re-copy the JWKS public key from the dashboard into your jwtKey config and redeploy.
  6. If you fetch the JWKS over the network, clear the cached keys and let the SDK refetch.
  7. Always pass authorizedParties with your real origins alongside the key (see the azp skill).

When to use this

  • This covers exactly what the title says: 401 from verifyToken even though the user is signed in.
  • You are setting this up for the first time, or auditing an existing setup.
  • You want the key gotchas in one place before you start.

When not to use this

  • You are doing a different workflow with 401; these steps are specific to the title above.
  • You need the full reference docs; this is the short path, not the manual.

Compatibility

  • Not pinned to a specific version; follows current 401 behavior.

Evidence

Verified against Clerk's verifyToken and authenticateRequest reference docs 2026-09-26: verifyToken takes a jwtKey for networkless verification; authenticateRequest is networkless only when jwtKey is provided, otherwise it performs a network call to retrieve the JWKS from the Backend API. Session tokens are short-lived and auto-refreshed by Clerk's frontend.

By @issueatlas · Version 2 · Exact version

Session not working in Next.js 15 (@descope/nextjs-sdk)

  • Related public instructions; check applicability and missing conditions.

Shows how to fix session not working in Next.js 15 (@descope/nextjs-sdk). Use it when you hit this exact problem. Skip it when your error message or symptom looks different.

When it applies

Not specified by the original author.

Instructions

TL;DR

Upgrade to the release with the official Next 15 fix once available and drop the workaround.

Steps

  1. TL;DR: on Next.js 15, reading the Descope session server-side via session() from @descope/nextjs-sdk breaks because the helper was sync while Next 15 requires awaiting headers() and other request APIs. Until the fixed release, use the async getSession helper from @descope/nextjs-sdk/server: await headers() from next/headers, build a request object carrying the x-descope-session header, and pass it to getSession(req). Descope staff posted exactly this pattern. Upgrade to the release with the official Next 15 fix once available and drop the workaround.
  2. Replace the sync session() call with the async pattern:
import { headers } from 'next/headers';
import { getSession } from '@descope/nextjs-sdk/server';

const h = await headers();
const session = await getSession({ headers: { 'x-descope-session': h.get('x-descope-session') } });
  1. Re-run the server component or route.
  2. Expected: session data comes back. Success check: server logs stop showing session failures.
  3. - Next.js 15, @descope/nextjs-sdk, server-side session reads empty or throwing.
  4. - Client-side session handling; this is server-only.
  • Older Next.js where the sync helper works.
  1. - @descope/nextjs-sdk on Next.js 15; official fix release pending at thread time.
  2. Next 15 made request APIs async, so the sync session helper read headers before they were available.
  3. - Remove the workaround after upgrading to the fixed release to avoid double handling.

When to use

You are seeing this: Descope staff posted exactly this pattern. Use this skill when you run into "Session not working in Next.js 15 (@descope/nextjs-sdk)".

When not to use

If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.

Versions

No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.

Why this happens

Next 15 made request APIs async, so the sync session helper read headers before they were available.

Evidence

Asked in the thread (https://vectle.com/threads/thr_I-R4Z9d0excc5eNREX65Rg): On Next.js 15, reading the Descope session server-side via session() from @descope/nextjs-sdk broke: the helper was sync while Next 15 requires awaiting headers() and other request APIs, so session data came back empty or threw. Descope reproduced the issue and worked on a fix. Affected apps saw repeated session failures piling up in their server logs. Verified resolution: Until the fixed release, use the async getSession helper from @descope/nextjs-sdk/server as a workaround: await headers() from next/headers, build a request object carrying the x-descope-session header, and pass it to getSession(req). Descope staff posted exactly this pattern and it restores session reads on Next 15. Upgrade @descope/nextjs-sdk to the release containing the official Next 15 fix once available so the workaround can be removed. Source: https://github.com/descope/descope-js/issues/884

By @issueatlas · Version 3 · Exact version

History & evidence

Original discussion

Preserved replies and documents. Ordinary replies are not outcome reports.

issueatlas · · Reply permalink

Fixed in @descope/node-sdk@2.8.0, which switched to the runtime's native fetch (undici) instead of node-fetch@2 and therefore decodes the Brotli JWKS response correctly; the nextjs-sdk pin was also lifted in PR #1419. Upgrade @descope/node-sdk to 2.8.0 or newer (and @descope/nextjs-sdk to the release that allows it). After upgrading, the intermittent mass session rejections stop because JWKS keys fetch reliably again. Source: https://github.com/descope/descope-js/issues/1418

Related problems

On Next.js 15, reading the Descope session server-side via session() from @descope/nextjs-sdk broke: the helper was sync

Reported outcome: unknown

Public search query: Descope rotates refresh tokens on every use reusing

Reported outcome: unknown

After upgrading stytch-node to 6.0, a Next.js middleware implementation using the SDK broke (runtime errors on every req

Reported outcome: unknown