farrow-auth-jwt
Version:
JWT authentication for Farrow HTTP framework
363 lines (276 loc) • 8.42 kB
Markdown
# farrow-auth-jwt
为 [Farrow](https://github.com/farrow-js/farrow) HTTP 框架提供的类型安全的 JWT 认证中间件,支持刷新令牌机制。
[English](./README.md) | 简体中文
## 特性
- 🔒 **双令牌系统** - Access Token + Refresh Token 机制
- 🎯 **类型安全** - 完整的 TypeScript 支持与类型推导
- 🛡️ **灵活的安全策略** - 支持令牌撤销、白名单规则、自定义验证
- 📦 **多种令牌来源** - 支持 Authorization 请求头、Cookie、查询参数
- ⚡ **React Hooks 风格** - 基于 Context 的状态管理
- 🔧 **高度可定制** - 自定义解析器、错误处理器和令牌存储
## 安装
```bash
npm install farrow-auth-jwt
# 或
yarn add farrow-auth-jwt
# 或
pnpm add farrow-auth-jwt
```
## 快速开始
```typescript
import { Http, Response } from 'farrow-http'
import { createJWTMiddleware, createJwtDataCtx } from 'farrow-auth-jwt'
// 定义用户类型
interface User {
id: number
username: string
role: 'admin' | 'user'
}
// 创建 JWT 上下文
const userContext = createJwtDataCtx<User>()
// 创建中间件
const jwtMiddleware = createJWTMiddleware({
access: {
secret: 'your-access-secret',
signOptions: { expiresIn: '15m' }
},
refresh: {
secret: 'your-refresh-secret',
signOptions: { expiresIn: '7d' }
},
jwtDataCtx: userContext,
whitelist: ['/login', '/register', '/refresh']
})
// 应用中间件
const app = Http()
app.use(jwtMiddleware)
// 登录端点
app.post('/login').use((request) => {
const { username, password } = request.body
// 验证凭据...
const user: User = { id: 1, username, role: 'user' }
// 签发令牌
return userContext.sign(user)
})
// 受保护的端点
app.get('/profile').use(() => {
const user = userContext.get()
// 注意:如果没有设置 passNoToken: true,无 token 的请求不会到达这里
// 中间件会直接返回 401,所以这里的 user 一定存在
return Response.json(user)
})
// 刷新端点
app.post('/refresh').use(async () => {
return await userContext.refresh()
})
app.listen(3000)
```
## 核心概念
### JWT 上下文
JWT 上下文扩展了 Farrow 的 Context 系统,添加了认证方法:
```typescript
const userContext = createJwtDataCtx<User>()
// 获取当前用户
const user = userContext.get()
// 签发新令牌
const response = userContext.sign(userData)
// 刷新令牌
const response = await userContext.refresh()
```
### 中间件选项
```typescript
interface JWTMiddlewareOptions<D> {
// Access Token 配置
access: {
secret: string | Buffer
signOptions?: jwt.SignOptions
verifyOptions?: jwt.VerifyOptions
}
// 可选的 Refresh Token 配置
refresh?: {
secret: string | Buffer
signOptions?: jwt.SignOptions
verifyOptions?: jwt.VerifyOptions
}
// JWT 数据上下文
jwtDataCtx: JwtDataCtx<D>
// 可选的令牌撤销检查器
isRevoked?: (payload: D) => boolean | Promise<boolean>
// 不需要认证的白名单路径
whitelist?: WhitelistRule[]
// 自定义令牌解析器
parser?: {
getToken: (request: RequestInfo) => {
accessToken: string | null
refreshToken: string | null
}
setToken: (token: string, refreshToken?: string) => Response
}
// 允许无令牌的请求继续执行
passNoToken?: boolean
}
```
## 高级用法
### 白名单规则
支持 [path-to-regexp](https://github.com/pillarjs/path-to-regexp) v8 语法的路径模式和 HTTP 方法限制:
```typescript
const whitelist: WhitelistRule[] = [
// 简单路径
'/public',
// 路径参数
'/users/:id', // 匹配 /users/123
'/api/users/:id?', // 可选参数,匹配 /api/users 和 /api/users/123
// 通配符(注意:v8 使用 {*} 语法)
'/public/{*path}', // 匹配 /public/* 的所有路径
// 带方法限制
{ path: '/auth/login', methods: ['POST'] },
{ path: '/api/upload', methods: ['POST', 'PUT'] }
]
```
### 令牌撤销
实现令牌黑名单或基于用户的撤销:
```typescript
const revokedTokens = new Set<string>()
const jwtMiddleware = createJWTMiddleware({
// ... 其他选项
isRevoked: async (payload: User) => {
// 检查用户是否被封禁
const user = await db.users.findById(payload.id)
return user.status === 'banned'
// 或检查令牌黑名单
// return revokedTokens.has(payload.jti)
}
})
```
### 自定义令牌解析器
自定义令牌的提取和返回方式:
```typescript
const customParser = {
getToken: (request: RequestInfo) => {
// 从自定义请求头提取
const accessToken = request.headers?.['x-access-token'] || null
const refreshToken = request.headers?.['x-refresh-token'] || null
return { accessToken, refreshToken }
},
setToken: (token: string, refreshToken?: string) => {
// 以自定义格式返回令牌
return Response.json({
auth: { accessToken: token, refreshToken },
expiresIn: 900
})
}
}
```
### 混合认证(公开 + 受保护)
允许同时支持已认证和匿名访问:
```typescript
const jwtMiddleware = createJWTMiddleware({
// ... 其他选项
passNoToken: true // 不拒绝没有令牌的请求
})
app.get('/posts').use(() => {
const user = userContext.get()
if (user) {
// 为已认证用户返回所有文章
return Response.json({ posts: getAllPosts(), user })
} else {
// 为匿名用户返回仅公开文章
return Response.json({ posts: getPublicPosts() })
}
})
```
### 错误处理
通过上下文访问 JWT 错误:
```typescript
import { JWTErrorContext } from 'farrow-auth-jwt'
app.use((request, next) => {
const response = next(request)
const error = JWTErrorContext.get()
if (error) {
// 记录认证错误
console.log('JWT 错误:', error)
// 自定义错误响应
switch (error.type) {
case 'TOKEN_EXPIRED':
return Response.status(401).json({
error: '会话已过期',
code: 'AUTH_EXPIRED'
})
case 'INVALID_TOKEN':
return Response.status(403).json({
error: '无效的凭据',
code: 'AUTH_INVALID'
})
// ... 处理其他错误
}
}
return response
})
```
## 令牌刷新流程
刷新令牌机制允许用户在不重新认证的情况下获取新的访问令牌:
```typescript
// 1. 配置刷新令牌
const jwtMiddleware = createJWTMiddleware({
access: {
secret: ACCESS_SECRET,
signOptions: { expiresIn: '15m' } // 短期有效
},
refresh: {
secret: REFRESH_SECRET,
signOptions: { expiresIn: '7d' } // 长期有效
},
jwtDataCtx: userContext,
whitelist: ['/auth/refresh'] // 重要:将刷新端点加入白名单
})
// 2. 登录返回两个令牌
app.post('/auth/login').use((request) => {
const user = validateCredentials(request.body)
return userContext.sign(user)
// 返回: { token: "...", refreshToken: "..." }
})
// 3. 刷新端点(必须在白名单中)
app.post('/auth/refresh').use(async (request) => {
// 只验证 refresh token,不需要 access token
return await userContext.refresh()
// 返回: { token: "new...", refreshToken: "new..." }
})
// 4. 客户端使用
// 当 access token 过期时,使用 refresh token 获取新令牌
fetch('/auth/refresh', {
method: 'POST',
body: JSON.stringify({ refreshToken: savedRefreshToken })
})
```
## API 参考
### `createJwtDataCtx<D>()`
创建一个用户数据类型为 `D` 的 JWT 上下文。
### `createJWTMiddleware<D>(options)`
创建 JWT 认证中间件。
### `verifyToken<D>(token, secret, options?)`
手动验证 JWT 令牌。返回 `Result<D, JWTError>`。
### `extractBearerToken(authHeader)`
从 Bearer 授权头中提取令牌。
### `JWTErrorContext`
包含当前 JWT 错误状态的上下文。
### 类型定义
```typescript
type JWTError =
| { type: 'TOKEN_EXPIRED'; expiredAt?: Date }
| { type: 'INVALID_TOKEN'; message: string }
| { type: 'NO_TOKEN' }
| { type: 'TOKEN_REVOKED' }
type WhitelistRule =
| string // 路径模式
| {
path: string
methods?: string[] // HTTP 方法
}
```
## 许可证
MIT
## 贡献
欢迎贡献!请随时提交 Pull Request。
## 相关链接
- [Farrow 框架](https://github.com/farrow-js/farrow)
- [JSON Web Tokens](https://jwt.io/)