Understanding the Incompatibility Between JWT Verify and NextAuth.js

Fatih İnci
3 min readJun 18, 2024

--

In the modern web development landscape, securing user authentication is paramount. Many developers turn to JSON Web Tokens (JWT) for their stateless, compact, and secure nature. When integrating authentication in a Next.js application, next-auth is a popular choice due to its simplicity and rich feature set.

However, a common stumbling block arises when developers try to use jwt.verify directly with tokens issued by next-auth. This article explores why this approach doesn't work and how to handle JWTs correctly within a next-auth setup.

The next-auth JWT Processnext-auth handles JWTs in a unique way to enhance security. Here’s a simplified view of the process:

  1. Encryption: When next-auth issues a JWT, it doesn't just sign it. It encrypts the token using the AES-256-GCM encryption algorithm. This provides an additional layer of security beyond mere signing.
  2. Key Derivation: The encryption key is derived using the HKDF (HMAC-based Extract-and-Expand Key Derivation Function). This method takes a secret and derives a secure encryption key, ensuring that even if the secret is compromised, the derived keys remain secure. Why jwt.verify Failsjsonwebtoken's jwt.verify function is designed to work with standard JWTs that are simply signed. It expects a token and a signing key (or public key for asymmetric algorithms). However, because next-auth encrypts its tokens, jwt.verify cannot handle these encrypted tokens.
  3. Encryption Mismatch: jwt.verify expects a signed JWT, not an encrypted one. The encrypted JWT from next-auth appears malformed because it doesn't conform to the expected structure of a signed JWT.
  4. Key Derivation: jwt.verify uses the provided secret directly as the signing key. In contrast, next-auth derives a unique encryption key from the secret, adding another layer of complexity that jwt.verify cannot process. Correctly Handling next-auth JWTsTo correctly handle JWTs issued by next-auth, you need to use the same encryption and key derivation mechanisms. Here’s how to do it using the jose library, which next-auth relies on:
  5. Install the jose library:
yarn add jose
  1. Decrypt the JWT:
import { jwtDecrypt } from "jose";
import hkdf from "@panva/hkdf";

async function getDerivedEncryptionKey(keyMaterial, salt) {
return await hkdf(
"sha256",
keyMaterial,
salt,
`NextAuth.js Generated Encryption Key${salt ? ` (${salt})` : ""}`,
32
);
}
async function decryptToken(token, secret) {
const encryptionKey = await getDerivedEncryptionKey(secret, "");
const { payload } = await jwtDecrypt(token, encryptionKey);
return payload;
}

This code snippet derives the encryption key using the same method as next-auth and then decrypts the token.

You can try run this code with ts-node test.ts in terminal

// run ts-node test.ts
const { jwtDecrypt } = require("jose");
const hkdf = require("@panva/hkdf").default;
async function getDerivedEncryptionKey(keyMaterial: string, salt: string) {
return await hkdf(
"sha256",
keyMaterial,
salt,
`NextAuth.js Generated Encryption Key${salt ? ` (${salt})` : ""}`,
32
);
}

async function decryptToken(token: string, secret: string) {
const encryptionKey = await getDerivedEncryptionKey(secret, "");
const { payload } = await jwtDecrypt(token, encryptionKey);
return payload;
}
const NEXTAUTH_SECRET = "xxx";
const YOUR_NEXTAUTH_JWT_TOKEN = "xxx";
(async () => {
const result = await decryptToken(YOUR_NEXTAUTH_JWT_TOKEN, NEXTAUTH_SECRET);
console.log(result);
})();

Conclusion

The mismatch between jwt.verify and next-auth tokens arises from next-auth's enhanced security measures involving token encryption and key derivation. To work with these tokens, you must use the same decryption process, typically involving the jose library. This ensures that your authentication mechanism remains robust and secure, adhering to the best practices implemented by next-auth. By understanding and correctly implementing these practices, you can effectively manage JWTs in your Next.js applications, leveraging the full security benefits of next-auth.

--

--