JWT_SECRET 是 undefined?Node.js import 链执行顺序导致环境变量未加载
在为客户构建 Node.js 后端时,登录接口返回 401,排查发现 JWT token 全部验证失败。日志显示 JWT_SECRET 的值是字符串 "undefined" 而非真正的密钥。记录根因与解法。
在为客户构建 Node.js 后端时,登录接口返回 401,排查发现 JWT token 全部验证失败。日志显示 JWT_SECRET 的值是字符串 "undefined" 而非真正的密钥。记录根因与解法。
在为客户构建 SaaS 认证系统时遇到此问题,记录根因与解法。
Node.js ES Module 中,import 语句在 dotenv.config() 之前执行。如果模块级代码读取 process.env.JWT_SECRET,拿到的是 undefined,导致 JWT 签名用 "undefined" 字符串作为密钥——不报错,但所有 token 校验都失败。解决方案:延迟初始化(lazy init)。
JWT 登录接口返回 200,但后续请求全部 401。排查发现:
jwtVerify() 验证process.env.JWT_SECRET,结果是 undefined// jwt.ts — 模块级代码
import crypto from 'crypto';
// ❌ 这行在 dotenv.config() 之前执行,JWT_SECRET 是 undefined
const SECRET = crypto.createSecretKey(
new TextEncoder().encode(process.env.JWT_SECRET)
);
最坑的是:不报错。new TextEncoder().encode(undefined) 会把字符串 "undefined" 编码成字节,生成一个合法但错误的密钥。
ES Module 的 import 是静态提升的:
// server.ts(入口文件)
import { router } from './routes/auth'; // ← 先执行
import { authenticateToken } from './middleware/auth'; // ← 先执行
dotenv.config(); // ← 后执行,但 import 链已经跑完了
执行顺序:
import,构建依赖图jwt.ts 的 const SECRET = ... 在这里执行)server.ts,执行 dotenv.config().env 才加载到 process.env所以 jwt.ts 模块级代码读到的 process.env.JWT_SECRET 是 undefined。
把密钥初始化从模块级移到函数内部,首次调用时才读取环境变量:
import crypto from 'crypto';
let _secret: crypto.KeyObject | null = null;
function getSecret(): crypto.KeyObject {
if (!_secret) {
const secretValue = process.env.JWT_SECRET;
if (!secretValue) {
throw new Error('JWT_SECRET 环境变量未设置');
}
_secret = crypto.createSecretKey(
new TextEncoder().encode(secretValue)
);
}
return _secret;
}
// 所有需要密钥的地方改用 getSecret()
export async function generateToken(payload: any): Promise<string> {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.sign(getSecret()); // ← 延迟到运行时读取
}
优势:不依赖入口文件的 import 顺序,任何调用时机都安全。
// server.ts — 确保这两行在所有 import 之前
import 'dotenv/config'; // 或 require('dotenv').config()
import express from 'express';
// ...其他 import
局限:如果有其他入口文件(如 cron job、worker)忘记加这行,问题复现。
require('dotenv').config() 只在 CommonJS 中能保证顺序;ES Module 中 import 始终先于运行时代码在为客户构建 SaaS 认证系统时遇到此问题,记录根因与解法。
Supabase Auth + FastAPI 集成有三个常见坑:JWKS 路径不是标准路径、ES256 签名需转换为 DER 格式、用户首次登录时本地数据库无记录。本文提供完整解决方案。
在为企业级 SaaS 系统升级 Node.js 版本时遇到此问题,记录根因与解法。
Node.js v24 对 Web Crypto API 的实现有变化,jose 库要求密钥必须是 KeyObject 或 CryptoKey 类型。用 crypto.createSecretKey() 包装密钥即可解决。