nextauthjs/next-auth · error

no matching decryption secret

Error message

no matching decryption secret

What it means

decode() in the Auth.js core tries each configured encryption secret to decrypt a JWE. It derives a thumbprint per secret and compares kid; if no secret matches, it throws 'no matching decryption secret'. This happens when the token was encrypted with a different secret than the ones currently configured.

Source

Thrown at packages/core/src/jwt.ts:100

  const { payload } = await jwtDecrypt(
    token,
    async ({ kid, enc }) => {
      for (const secret of secrets) {
        const encryptionSecret = await getDerivedEncryptionKey(
          enc,
          secret,
          salt
        )
        if (kid === undefined) return encryptionSecret

        const thumbprint = await calculateJwkThumbprint(
          { kty: "oct", k: base64url.encode(encryptionSecret) },
          `sha${encryptionSecret.byteLength << 3}` as Digest
        )
        if (kid === thumbprint) return encryptionSecret
      }

      throw new Error("no matching decryption secret")
    },
    {
      clockTolerance: 15,
      keyManagementAlgorithms: [alg],
      contentEncryptionAlgorithms: [enc, "A256GCM"],
    }
  )
  return payload as Payload
}

type GetTokenParamsBase = {
  secret?: JWTDecodeParams["secret"]
  salt?: JWTDecodeParams["salt"]
}

export interface GetTokenParams<R extends boolean = false>
  extends GetTokenParamsBase {
  /** The request containing the JWT either in the cookies or in the `Authorization` header. */

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Set the same AUTH_SECRET (or `secret` option) that was used to encrypt the token
  2. If rotating, include the old secret(s) in the `secret` array so existing tokens still decrypt — new tokens use the first
  3. Confirm the token's kid/alg/enc match the adapter configuration (default dir/dir + A256GCM)
  4. Re-issue the token/session if the original secret is unrecoverable (users must sign in again)

Example fix

// before
jwtDecode({ token, secret: process.env.NEW_SECRET })
// after
jwtDecode({
  token,
  secret: [process.env.NEW_SECRET, process.env.OLD_SECRET], // old secret keeps existing tokens working
})
Defensive patterns

Strategy: fallback

Validate before calling

if (!process.env.AUTH_SECRET) throw new Error('AUTH_SECRET missing — tokens cannot be decrypted')

Try / catch

try {
  return await decode({ token, secret, ...params })
} catch (e) {
  if (e.message === 'no matching decryption secret') {
    // secret mismatch/rotation — force re-authentication
    return null
  }
  throw e
}

Prevention

When it happens

Trigger: AUTH_SECRET/NEXTAUTH_SECRET changed or rotated after tokens were issued; multiple secrets configured but none matches the token's kid; token encrypted with a different alg/enc than allowed (keyManagementAlgorithms/contentEncryptionAlgorithms); decoding a token from another environment (staging vs prod).

Common situations: Deployments where the secret env var differs between build and runtime; secret rotation invalidating active sessions; sharing tokens across services with different secrets; debugging getToken() on tokens minted before a secret change.

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/66d8539895c348ca. Report an issue: GitHub.