@db-node/jwt
v0.1.1
Published
Tiny, type-safe JWT toolkit on top of jsonwebtoken — sign / verify / decode / expiry check, with factory & functional APIs.
Maintainers
Readme
@db-node/jwt
Tiny, type-safe JWT toolkit on top of
jsonwebtoken— sign / verify / decode / expiry check, with both functional and factory APIs.
- ✅ TypeScript 优先,完整泛型推断
- ✅ 同时提供函数式 API与实例化工厂
createJwt - ✅ 安全默认:禁用
'none'算法、verify 时强制 algorithm 白名单 - ✅ 提供"不抛错"的
verifySafe,错误结构化分类 - ✅ 不验签的
decode / isExpired / getRemainingTtl工具函数 - ✅
extractBearer一行解析Authorizationheader
Install
pnpm add @db-node/jwt
# or: npm i @db-node/jwtQuick Start
函数式 API(一次性调用 / 简单场景)
import { sign, verify, isExpired } from '@db-node/jwt';
const token = sign({ userId: 1, role: 'admin' }, 'my-secret', {
expiresIn: '1h',
});
const payload = verify<{ userId: number; role: string }>(token, 'my-secret');
// payload.userId === 1
if (isExpired(token)) {
// 主动刷新
}实例化 API(业务推荐)
import { createJwt } from '@db-node/jwt';
const jwt = createJwt({
secret: process.env.JWT_SECRET!,
expiresIn: '1h',
issuer: 'my-app',
});
const token = jwt.sign({ userId: 1 });
const payload = jwt.verify(token); // 失败抛错
const safe = jwt.verifySafe(token); // 不抛错,结构化结果API
sign(payload, secret, options?) → string
签发一个 JWT token。
| 参数 | 类型 | 说明 |
| --------- | ------------------------------------ | --------------------------------------------------- |
| payload | T extends Record<string, any> | 业务载荷 |
| secret | string \| Buffer \| KeyObject | HS* 用共享密钥;RS* / ES* / PS* 用私钥 |
| options | SignOpts | 可选;如 expiresIn / algorithm / issuer 等 |
const token = sign(
{ userId: 1 },
fs.readFileSync('private.pem'),
{ algorithm: 'RS256', expiresIn: '7d', issuer: 'my-app' },
);verify(token, secret, options?) → T
验证签名 + 标准 claim(exp/nbf/iss/aud/sub),失败抛错。
可能抛出:
TokenExpiredError— token 已过期NotBeforeError— 未到nbf生效时间JsonWebTokenError— 签名错误 / 算法不匹配 / token 格式非法
try {
const payload = verify<{ userId: number }>(token, secret);
} catch (e: any) {
if (e.name === 'TokenExpiredError') /* ... */;
}verifySafe(token, secret, options?) → VerifyResult<T>
与 verify 行为一致,但永不抛错,返回结构化结果。
type VerifyResult<T> =
| { ok: true; payload: T }
| { ok: false; error: { kind: VerifyErrorKind; message: string; cause?: unknown } };
type VerifyErrorKind =
| 'TokenExpiredError'
| 'NotBeforeError'
| 'JsonWebTokenError'
| 'UnknownError';const r = verifySafe<{ uid: number }>(token, secret);
if (!r.ok) {
switch (r.error.kind) {
case 'TokenExpiredError': ctx.throw(401, 'expired'); break;
case 'NotBeforeError': ctx.throw(401, 'not active'); break;
default: ctx.throw(401, 'invalid token');
}
}decode<T>(token) → T | null
仅 base64url 解码 payload,不验证签名。token 格式非法返回 null。
const p = decode<{ exp: number }>(token);
console.log(p?.exp);decodeComplete<T>(token) → { header, payload, signature } | null
完整解码,可读 header 中的 kid / alg 等。
const full = decodeComplete(token);
const kid = full?.header.kid;isExpired(token, leewaySec?) → boolean | null
仅根据 payload.exp 判断是否过期,不验证签名。
| 返回值 | 含义 |
| ------- | ----------------------------------- |
| true | 已过期 |
| false | 未过期 |
| null | 无 exp 字段 / token 格式非法 |
if (isExpired(token, 30) === true) refresh(); // 容忍 30s 漂移getRemainingTtl(token) → number | null
返回 token 剩余有效秒数(已过期返回 0,无 exp 返回 null)。
const ttl = getRemainingTtl(token);
if (ttl !== null && ttl < 300) refresh(); // 5min 内过期则刷新extractBearer(header) → string | null
从 Authorization header 中提取 token,兼容大小写与多余空格。
const token = extractBearer(req.headers.authorization);
// ^ "Bearer xxx.yyy.zzz" → "xxx.yyy.zzz"createJwt(options) → JwtInstance
创建一个 JWT 实例,复用默认配置。所有方法的最后一个参数 overrides 都可临时覆盖默认值。
const jwt = createJwt({
secret: 'my-secret', // HS* 必填;非对称时这里放私钥
publicKey: undefined, // 非对称时这里放公钥(HS* 留空)
algorithm: 'HS256', // 默认 HS256
expiresIn: '1h',
issuer: 'my-app',
audience: 'web',
subject: undefined,
clockTolerance: 5, // 容忍 5s 时钟漂移
});
jwt.sign({ userId: 1 }); // 用默认配置签发
jwt.sign({ userId: 1 }, { expiresIn: '7d' }); // 临时改 7d
jwt.verify(token);
jwt.verifySafe(token);
jwt.isExpired(token);
jwt.getRemainingTtl(token);
jwt.decode(token);
jwt.decodeComplete(token);
jwt.extractBearer(authHeader);
jwt.options; // 只读快照非对称算法(RS256 示例)
import fs from 'node:fs';
import { createJwt } from '@db-node/jwt';
const jwt = createJwt({
algorithm: 'RS256',
secret: fs.readFileSync('private.pem'), // 签发用私钥
publicKey: fs.readFileSync('public.pem'), // 验证用公钥
expiresIn: '7d',
issuer: 'my-app',
});自定义每次签发的 key
若不同请求需要不同密钥,建议使用函数式 API:
import { sign, verify } from '@db-node/jwt';
const token = sign({ x: 1 }, lookupSecretByTenant(tenantId), { expiresIn: '1h' });
const data = verify(token, lookupSecretByTenant(tenantId));Types
SignOpts
jsonwebtoken 原生 SignOptions 的子集(algorithm 收紧到白名单)。常用:
expiresIn: '1h' | '7d' | numbernotBefore: string | numberalgorithm: JwtAlgorithmissuer / audience / subject / jwtidheader(自定义 JOSE header)keyid(→ header.kid)
VerifyOpts
jsonwebtoken 原生 VerifyOptions(algorithms 收紧到白名单)。常用:
algorithms: JwtAlgorithm[](防降级)issuer / audience / subjectclockTolerance: numbermaxAge: string | numberignoreExpiration: boolean
JwtAlgorithm
HS256 | HS384 | HS512
RS256 | RS384 | RS512
ES256 | ES384 | ES512
PS256 | PS384 | PS512⚠️ 'none' 已被禁用(防降级攻击)。
Legacy API(旧项目无痛迁移)
如果你的旧项目长这样:
// jwt.js(旧)
const jwt = require('jsonwebtoken');
const { privatekey } = require('../comm/constant');
const generateToken = (userName) => jwt.sign({ username: userName }, privatekey, { expiresIn: '30d' });
const validToken = (token) => { /* ... */ };
const tokenUserName = (token) => { /* ... */ };
const tokenUserNamePromise = (token) => { /* ... */ };
const decryptToken = (token) => { /* ... */ };
module.exports = { jwt, privatekey, generateToken, decryptToken, validToken, tokenUserName, tokenUserNamePromise };只需替换为:
// jwt.js(新)
const { createLegacyAuth } = require('@db-node/jwt');
const { privatekey } = require('../comm/constant');
module.exports = createLegacyAuth(privatekey);
// 可选:自定义过期时间
// module.exports = createLegacyAuth(privatekey, { expiresIn: '7d' });业务侧 require('./jwt') 解构出来的 7 个字段一字不差。
Legacy 函数清单
| 字段 | 类型 | 说明 |
| -------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------- |
| jwt | typeof jsonwebtoken | 透传 jsonwebtoken |
| privatekey | JwtSecret | 初始化时传入的密钥回显 |
| generateToken(userName) | (userName) => string | 签发 token,payload = { username: userName },默认 30 天 |
| decryptToken(token) | (token) => object \| null | 不验签解码(修复了旧版 bug:旧版会把 token 当 username 再签发再解码) |
| validToken(token) | (token) => { err_code, data, message } | 合法 {200, true, '成功'} / 非法 {100, false, 'token过期或无效'} |
| tokenUserName(token) | 成功返回 payload / 失败返回 err_code 对象 | 注意返回结构异质(保留旧契约,建议新代码改用 verifySafe) |
| tokenUserNamePromise(token) | (token) => Promise<payload \| err_code> | Promise 版,永不 reject(修复了旧版 try/catch 不生效的问题) |
⚠️ 与原实现的两处语义修复:
decryptToken现在真的解码传入的 token(旧版是decode(generateToken(token)),是 bug)tokenUserNamePromise失败时 resolve 错误对象(旧版外层 try/catch 实际不会触发,verify 同步抛错会被 Promise 构造函数捕获并 reject,外部await会拿到拒绝)
安全建议
- 密钥:HS* 建议 ≥ 32 字节;RS* / ES* 使用标准 PEM。
- algorithms 白名单:实例化 API 默认强制约束,无需额外配置。
exp必须设置:所有 token 都建议指定expiresIn,避免长期有效。- 不要用
decode做鉴权:它不验签,仅适合读取元信息。 issuer / audience校验:多服务共享时务必配置,防止 token 跨域被复用。
License
MIT
