npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill auth-flow-validationsecurity 技能oauth2 技能jwt 技能深度知识:使用
mcp__documentation__fetch_docs并指定技术:jwt或oauth2以获取协议详情。
┌─────────────────────────────────────────────────────────────────────┐
│ 认证流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 前端 后端 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 1. 登录 │──────────→│ POST /auth │ │
│ │ 表单 │ │ /login │ │
│ └──────────────┘ └──────┬───────┘ │
│ ↑ │ │
│ │ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 2. 存储 │←──────────│ 返回 JWT │ │
│ │ 令牌 │ │ + 刷新令牌 │ │
│ └──────────────┘ └──────────────┘ │
│ │ │
│ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 3. API 调用 │──────────→│ 验证 JWT │ │
│ │ + 认证头信息 │ │ │ │
│ └──────────────┘ └──────┬───────┘ │
│ ↑ │ │
│ │ 401 未授权 │ │
│ │←─────────────────────────┤ │
│ │ │ │
│ ↓ │ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 4. 刷新 │──────────→│ POST /auth │ │
│ │ 令牌 │ │ /refresh │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 步骤 | 前端 | 后端 | 验证 |
|---|---|---|---|
| 端点 | POST /auth/login | POST /auth/login | 路径匹配 |
| 请求体 | { email, password } | { email, password } | 模式匹配 |
| 响应 | { accessToken, refreshToken, user } | 相同结构 | 类型匹配 |
| 令牌存储 | localStorage/httpOnly cookie | 期望 cookie 或 header | 方法匹配 |
| 错误响应 | 处理 401, 400 | 返回结构化错误 | 错误格式 |
| 步骤 | 前端 | 后端 | 验证 |
|---|---|---|---|
| 触发条件 | 拦截 401 响应 | 令牌过期时返回 401 | 行为一致 |
| 端点 | POST /auth/refresh | POST /auth/refresh | 路径匹配 |
| 请求 | refreshToken 在 body/cookie 中 | 期望相同位置 | 方法匹配 |
| 响应 | 新的 accessToken | 返回新令牌 | 结构匹配 |
| 重试 | 重试原始请求 | 接受新令牌 | 流程完成 |
// 后端期望:HttpOnly cookie
res.cookie('accessToken', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
});
// 前端存储:localStorage (不匹配!)
localStorage.setItem('accessToken', response.accessToken);
// 前端应该:让后端设置 cookie
// 无需手动存储 - cookie 自动发送
// 后端期望
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// 前端发送 (错误)
Authorization: eyJhbGciOiJIUzI1NiIs... // 缺少 "Bearer "
// 前端发送 (错误)
Authorization: bearer eyJhbGciOiJIUzI1NiIs... // 大小写错误
// 正确的前端实现
const token = getAccessToken();
fetch('/api/users', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
// 后端期望:Cookie
app.post('/auth/refresh', (req, res) => {
const refreshToken = req.cookies.refreshToken; // 来自 cookie
});
// 前端发送:Body (不匹配!)
fetch('/auth/refresh', {
method: 'POST',
body: JSON.stringify({ refreshToken }), // 错误!
});
// 正确:Cookie 自动发送
fetch('/auth/refresh', {
method: 'POST',
credentials: 'include', // 包含 cookies
});
// 后端:令牌 15 分钟后过期
const token = jwt.sign(payload, secret, { expiresIn: '15m' });
// 前端:无过期检查 (不好)
function makeAuthenticatedRequest() {
return fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` },
});
}
// 前端:请求前检查过期 (好)
function makeAuthenticatedRequest() {
const token = getAccessToken();
if (isTokenExpired(token)) {
await refreshAccessToken();
}
return fetch('/api/data', {
headers: { Authorization: `Bearer ${getAccessToken()}` },
});
}
// 检查过期的辅助函数
function isTokenExpired(token: string): boolean {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp * 1000 < Date.now();
}
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
});
// 请求拦截器 - 添加令牌
api.interceptors.request.use((config) => {
const token = getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器 - 处理 401
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newToken = await refreshAccessToken();
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return api(originalRequest);
} catch (refreshError) {
// 刷新失败 - 注销用户
logout();
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
async function fetchWithAuth(url: string, options: RequestInit = {}) {
let token = getAccessToken();
// 检查令牌是否过期
if (isTokenExpired(token)) {
token = await refreshAccessToken();
}
const response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
// 处理 401 (服务器拒绝令牌)
if (response.status === 401) {
token = await refreshAccessToken();
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
}
return response;
}
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: (failureCount, error) => {
// 401 时不重试
if (error instanceof Response && error.status === 401) {
return false;
}
return failureCount < 3;
},
},
},
});
// 全局错误处理器
queryClient.setDefaultOptions({
mutations: {
onError: (error) => {
if (error instanceof Response && error.status === 401) {
// 重定向到登录
window.location.href = '/login';
}
},
},
});
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
paths:
/users:
get:
security:
- bearerAuth: []
responses:
401:
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: accessToken
paths:
/users:
get:
security:
- cookieAuth: []
## 认证流程验证报告
### 登录流程
| 检查项 | 状态 | 详情 |
|-------|--------|---------|
| 端点路径 | OK | POST /auth/login |
| 请求体 | OK | { email, password } |
| 响应结构 | WARNING | 缺少 `expiresIn` 字段 |
| 令牌存储 | ERROR | 前端使用 localStorage,后端期望 cookie |
### 令牌刷新流程
| 检查项 | 状态 | 详情 |
|-------|--------|---------|
| 触发条件 | OK | 401 被拦截 |
| 刷新端点 | OK | POST /auth/refresh |
| 令牌位置 | ERROR | 前端在 body 中发送,后端期望 cookie |
| 重试机制 | OK | 原始请求被重试 |
### 建议
1. **关键**:将令牌存储更改为 httpOnly cookies
2. **关键**:更新刷新以使用 credentials: 'include'
3. **警告**:在登录响应中添加 expiresIn 以便主动刷新
| 反模式 | 为何不好 | 正确方法 |
|---|---|---|
| localStorage 存储令牌 | XSS 漏洞 | 使用 httpOnly cookies |
| 无令牌刷新 | 用户体验差,强制登出 | 实现刷新流程 |
| 每次请求都刷新 | 性能问题 | 在 401 或接近过期时刷新 |
| 刷新失败时不登出 | 安全风险 | 清除令牌并重定向 |
| 硬编码令牌过期时间 | 与后端不同步 | 从令牌或响应中解析 |
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 登录时 401 | 凭据格式错误 | 检查请求体模式 |
| 刷新后 401 | 刷新令牌也已过期 | 重新认证用户 |
| 无限刷新循环 | 未标记重试请求 | 添加重试标志 |
| 认证端点 CORS 错误 | 缺少凭据配置 | 添加 credentials: 'include' |
| 令牌未发送 | 授权头格式错误 | 检查 "Bearer " 前缀 |
每周安装数
1
仓库
首次出现
3 天前
安全审计
安装于
amp1
cline1
openclaw1
opencode1
cursor1
kimi-cli1
security skillsoauth2 skilljwt skillDeep Knowledge : Use
mcp__documentation__fetch_docswith technology:jwtoroauth2for protocol details.
┌─────────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Frontend Backend │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 1. Login │──────────→│ POST /auth │ │
│ │ Form │ │ /login │ │
│ └──────────────┘ └──────┬───────┘ │
│ ↑ │ │
│ │ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 2. Store │←──────────│ Return JWT │ │
│ │ Tokens │ │ + Refresh │ │
│ └──────────────┘ └──────────────┘ │
│ │ │
│ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 3. API Call │──────────→│ Validate JWT │ │
│ │ + Auth Header│ │ │ │
│ └──────────────┘ └──────┬───────┘ │
│ ↑ │ │
│ │ 401 Unauthorized │ │
│ │←─────────────────────────┤ │
│ │ │ │
│ ↓ │ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 4. Refresh │──────────→│ POST /auth │ │
│ │ Token │ │ /refresh │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
| Step | Frontend | Backend | Validation |
|---|---|---|---|
| Endpoint | POST /auth/login | POST /auth/login | Path match |
| Request Body | { email, password } | { email, password } | Schema match |
| Response | { accessToken, refreshToken, user } | Same structure | Type match |
| Token Storage | localStorage/httpOnly cookie | Expects cookie or header | Method match |
| Error Response | Handle 401, 400 | Returns structured error | Error format |
| Step | Frontend | Backend | Validation |
|---|---|---|---|
| Trigger | 401 response intercepted | Returns 401 on expired | Consistent behavior |
| Endpoint | POST /auth/refresh | POST /auth/refresh | Path match |
| Request | refreshToken in body/cookie | Expects same location | Method match |
| Response | New accessToken | Returns new token | Structure match |
| Retry | Original request retried | Accepts new token | Flow complete |
// Backend expects: HttpOnly cookie
res.cookie('accessToken', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
});
// Frontend stores: localStorage (MISMATCH!)
localStorage.setItem('accessToken', response.accessToken);
// Frontend should: Let backend set cookie
// No manual storage needed - cookie sent automatically
// Backend expects
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// Frontend sends (WRONG)
Authorization: eyJhbGciOiJIUzI1NiIs... // Missing "Bearer "
// Frontend sends (WRONG)
Authorization: bearer eyJhbGciOiJIUzI1NiIs... // Wrong case
// Correct frontend implementation
const token = getAccessToken();
fetch('/api/users', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
// Backend expects: Cookie
app.post('/auth/refresh', (req, res) => {
const refreshToken = req.cookies.refreshToken; // From cookie
});
// Frontend sends: Body (MISMATCH!)
fetch('/auth/refresh', {
method: 'POST',
body: JSON.stringify({ refreshToken }), // Wrong!
});
// Correct: Cookie sent automatically
fetch('/auth/refresh', {
method: 'POST',
credentials: 'include', // Include cookies
});
// Backend: Token expires after 15 minutes
const token = jwt.sign(payload, secret, { expiresIn: '15m' });
// Frontend: No expiration check (BAD)
function makeAuthenticatedRequest() {
return fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` },
});
}
// Frontend: Check expiration before request (GOOD)
function makeAuthenticatedRequest() {
const token = getAccessToken();
if (isTokenExpired(token)) {
await refreshAccessToken();
}
return fetch('/api/data', {
headers: { Authorization: `Bearer ${getAccessToken()}` },
});
}
// Helper to check expiration
function isTokenExpired(token: string): boolean {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp * 1000 < Date.now();
}
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
});
// Request interceptor - add token
api.interceptors.request.use((config) => {
const token = getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor - handle 401
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newToken = await refreshAccessToken();
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return api(originalRequest);
} catch (refreshError) {
// Refresh failed - logout user
logout();
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
async function fetchWithAuth(url: string, options: RequestInit = {}) {
let token = getAccessToken();
// Check if token expired
if (isTokenExpired(token)) {
token = await refreshAccessToken();
}
const response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
// Handle 401 (token rejected by server)
if (response.status === 401) {
token = await refreshAccessToken();
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
}
return response;
}
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: (failureCount, error) => {
// Don't retry on 401
if (error instanceof Response && error.status === 401) {
return false;
}
return failureCount < 3;
},
},
},
});
// Global error handler
queryClient.setDefaultOptions({
mutations: {
onError: (error) => {
if (error instanceof Response && error.status === 401) {
// Redirect to login
window.location.href = '/login';
}
},
},
});
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
paths:
/users:
get:
security:
- bearerAuth: []
responses:
401:
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: accessToken
paths:
/users:
get:
security:
- cookieAuth: []
## Auth Flow Validation Report
### Login Flow
| Check | Status | Details |
|-------|--------|---------|
| Endpoint path | OK | POST /auth/login |
| Request body | OK | { email, password } |
| Response structure | WARNING | Missing `expiresIn` field |
| Token storage | ERROR | Frontend uses localStorage, backend expects cookie |
### Token Refresh Flow
| Check | Status | Details |
|-------|--------|---------|
| Trigger condition | OK | 401 intercepted |
| Refresh endpoint | OK | POST /auth/refresh |
| Token placement | ERROR | Frontend sends in body, backend expects cookie |
| Retry mechanism | OK | Original request retried |
### Recommendations
1. **Critical**: Change token storage to httpOnly cookies
2. **Critical**: Update refresh to use credentials: 'include'
3. **Warning**: Add expiresIn to login response for proactive refresh
| Anti-Pattern | Why It's Bad | Correct Approach |
|---|---|---|
| localStorage for tokens | XSS vulnerability | Use httpOnly cookies |
| No token refresh | Poor UX, forced logout | Implement refresh flow |
| Refresh on every request | Performance | Refresh on 401 or near expiry |
| No logout on refresh fail | Security risk | Clear tokens and redirect |
| Hardcoded token expiry | Drift with backend | Parse from token or response |
| Issue | Likely Cause | Solution |
|---|---|---|
| 401 on login | Wrong credentials format | Check request body schema |
| 401 after refresh | Refresh token also expired | Re-authenticate user |
| Infinite refresh loop | Not marking retried requests | Add retry flag |
| CORS on auth endpoints | Missing credentials config | Add credentials: 'include' |
| Token not sent | Authorization header format | Check "Bearer " prefix |
Weekly Installs
1
Repository
First Seen
3 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
amp1
cline1
openclaw1
opencode1
cursor1
kimi-cli1
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
150,000 周安装
Azure 可观测性服务指南:Monitor、App Insights、Log Analytics 监控与 KQL 查询
102,200 周安装
Azure 资源查找工具:跨订阅查询、发现孤立资源、审计标签配置
103,000 周安装
Microsoft Foundry 技能指南:部署、调用、监控智能体全流程详解
103,100 周安装
Microsoft Entra应用注册指南:Azure AD应用配置、API权限与OAuth流程详解
103,100 周安装
Azure 验证工具 - Microsoft GitHub Copilot for Azure 部署前检查指南
103,100 周安装
Azure资源可视化工具 - 自动生成架构图,分析资源依赖关系
103,100 周安装