diff --git a/.changeset/young-beans-trade.md b/.changeset/young-beans-trade.md new file mode 100644 index 0000000000..821697a2c8 --- /dev/null +++ b/.changeset/young-beans-trade.md @@ -0,0 +1,5 @@ +--- +'@clerk/nextjs': patch +--- + +Improves the error message when changing keys in development causes a subsequent request to fail. diff --git a/packages/nextjs/src/server/errors.ts b/packages/nextjs/src/server/errors.ts index d2bc6d6c93..c519771a7e 100644 --- a/packages/nextjs/src/server/errors.ts +++ b/packages/nextjs/src/server/errors.ts @@ -41,3 +41,5 @@ ${verifyMessage}`; export const authSignatureInvalid = `Clerk: Unable to verify request, this usually means the Clerk middleware did not run. Ensure Clerk's middleware is properly integrated and matches the current route. For more information, see: https://clerk.com/docs/references/nextjs/clerk-middleware. (code=auth_signature_invalid)`; export const encryptionKeyInvalid = `Clerk: Unable to decrypt request data, this usually means the encryption key is invalid. Ensure the encryption key is properly set. For more information, see: https://clerk.com/docs/references/nextjs/clerk-middleware#dynamic-keys. (code=encryption_key_invalid)`; + +export const encryptionKeyInvalidDev = `Clerk: Unable to decrypt request data.\n\nRefresh the page if your .env file was just updated. If the issue persists, ensure the encryption key is valid and properly set.\n\nFor more information, see: https://clerk.com/docs/references/nextjs/clerk-middleware#dynamic-keys. (code=encryption_key_invalid)`; diff --git a/packages/nextjs/src/server/utils.ts b/packages/nextjs/src/server/utils.ts index 65fbe511fb..39d561ae39 100644 --- a/packages/nextjs/src/server/utils.ts +++ b/packages/nextjs/src/server/utils.ts @@ -11,8 +11,15 @@ import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; import { constants as nextConstants } from '../constants'; +import { canUseKeyless__server } from '../utils/feature-flags'; import { DOMAIN, ENCRYPTION_KEY, IS_SATELLITE, PROXY_URL, SECRET_KEY, SIGN_IN_URL } from './constants'; -import { authSignatureInvalid, encryptionKeyInvalid, missingDomainAndProxy, missingSignInUrlInDev } from './errors'; +import { + authSignatureInvalid, + encryptionKeyInvalid, + encryptionKeyInvalidDev, + missingDomainAndProxy, + missingSignInUrlInDev, +} from './errors'; import { errorThrower } from './errorThrower'; import type { RequestLike } from './types'; @@ -280,10 +287,34 @@ export function decryptClerkRequestData( : ENCRYPTION_KEY || SECRET_KEY || KEYLESS_ENCRYPTION_KEY; try { - const decryptedBytes = AES.decrypt(encryptedRequestData, maybeKeylessEncryptionKey); - const encoded = decryptedBytes.toString(encUtf8); - return JSON.parse(encoded); + return decryptData(encryptedRequestData, maybeKeylessEncryptionKey); } catch (err) { + /** + * There is a great chance when running on Keyless mode that the above fails, + * because the keys hot-swapped and the Next.js dev server has not yet fully rebuilt middleware and routes. + * + * Attempt one more time with the default dummy value. + */ + if (canUseKeyless__server) { + try { + return decryptData(encryptedRequestData, KEYLESS_ENCRYPTION_KEY); + } catch (e) { + throwInvalidEncryptionKey(); + } + } + throwInvalidEncryptionKey(); + } +} + +function throwInvalidEncryptionKey(): never { + if (isProductionEnvironment()) { throw new Error(encryptionKeyInvalid); } + throw new Error(encryptionKeyInvalidDev); +} + +function decryptData(data: string, key: string) { + const decryptedBytes = AES.decrypt(data, key); + const encoded = decryptedBytes.toString(encUtf8); + return JSON.parse(encoded); }