SuperAPI AI Directory

auth-e2e

Security & auditing

View repository

Updated 10-16 09:30

About this skill

End-to-End Authentication (Auth E2E)

Use Cases

  • Full-stack project authentication implementation (registration → login → permissions)
  • NextAuth / Lucia / Supabase Auth integration
  • Custom JWT + Refresh Token
  • Frontend route guards + backend middleware
  • Social login (Google / GitHub)

Core Principles

1. 认证是端到端的
   DB users 表 + 后端 JWT + 前端守卫 = 一起做

2. 不自己写加密
   用 bcrypt / argon2
  1. Secure Token storage HttpOnly Cookie (recommended) > localStorage

  2. Frontend guards ≠ security The backend must verify; the frontend is only UX

  3. Prefer ready-made solutions NextAuth / Lucia / Supabase Auth > custom


## 方案选型

| 方案 | 适合 | 复杂度 |
|---|---|---|
| **NextAuth.js (Auth.js)** | Next.js 项目 | 低 |
| **Lucia** | 任意 TS 框架 | 中 |
| **Supabase Auth** | Supabase 项目 | 低 |
| **Clerk** | SaaS(付费) | 极低 |
| **自建 JWT** | 完全控制 | 高 |
| **Django Auth** | Django 项目 | 低 |
| **Devise** | Rails 项目 | 低 |

## NextAuth.js(Next.js 推荐)

```typescript
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      async authorize(credentials) {
        const user = await db.user.findUnique({ where: { email: credentials.email } });
        if (!user) return null;
        const valid = await bcrypt.compare(credentials.password, user.password);
        if (!valid) return null;
        return { id: user.id, email: user.email, role: user.role };
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) { token.role = user.role; token.id = user.id; }
      return token;
    },
    async session({ session, token }) {
      session.user.role = token.role;
      session.user.id = token.id;
      return session;
    },
  },
});

Custom JWT (full control)

Backend

// 注册
app.post('/auth/register', async (req, res) => {
  const { email, password, name } = registerSchema.parse(req.body);
  const hash = await bcrypt.hash(password, 12);
  const user = await db.user.create({ data: { email, password: hash, name } });
  const token = generateAccessToken(user);
  const refresh = generateRefreshToken(user);
  await db.refreshToken.create({ data: { token: refresh, userId: user.id } });
  
  res.cookie('refresh_token', refresh, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 7 * 24 * 60 * 60 * 1000 });
  res.json({ accessToken: token, user: { id: user.id, email, name } });
});

// 登录
app.post('/auth/login', async (req, res) => {
  const { email, password } = loginSchema.parse(req.body);
  const user = await db.user.findUnique({ where: { email } });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ error: { code: 'INVALID_CREDENTIALS' } });
  }
  // ... 同上生成 token
});

// 刷新
app.post('/auth/refresh', async (req, res) => {
  const refreshToken = req.cookies.refresh_token;
  if (!refreshToken) return res.status(401).json({ error: { code: 'NO_REFRESH_TOKEN' } });
  
  const stored = await db.refreshToken.findUnique({ where: { token: refreshToken } });
  if (!stored) return res.status(401).json({ error: { code: 'INVALID_REFRESH_TOKEN' } });
  
  const user = await db.user.findUnique({ where: { id: stored.userId } });
  const newAccess = generateAccessToken(user);
  res.json({ accessToken: newAccess });
});

// 中间件
function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: { code: 'UNAUTHORIZED' } });
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    return res.status(401).json({ error: { code: 'TOKEN_EXPIRED' } });
  }
}

Frontend

// 登录
async function login(email: string, password: string) {
  const res = await api.post('/auth/login', { email, password });
  setAccessToken(res.data.accessToken);
  setUser(res.data.user);
  router.push('/dashboard');
}

// Token 刷新(axios 拦截器)
api.interceptors.response.use(
  (res) => res,
  async (error) => {
    if (error.response?.status === 401 && !error.config._retry) {
      error.config._retry = true;
      const { data } = await api.post('/auth/refresh');
      setAccessToken(data.accessToken);
      error.config.headers.Authorization = `Bearer ${data.accessToken}`;
      return api(error.config);
    }
    return Promise.reject(error);
  }
);

// 路由守卫(Next.js middleware)
export function middleware(request: NextRequest) {
  const token = request.cookies.get('access_token');
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

End-to-End Flow

1. DB:users 表(email, password_hash, role)
2. 后端:注册 / 登录 / 刷新 / 中间件
3. 前端:登录表单 / Token 存储 / 路由守卫
4. 联调:Token 传递 / 刷新 / 过期处理
5. 测试:登录成功 / 失败 / 越权

Supporting Templates

  • templates/auth-checklist.md — Authentication end-to-end checklist

Quality Self-Check

□ 密码 bcrypt / argon2
□ JWT 有过期时间(15 min)
□ Refresh Token 可撤销
□ Token 存 HttpOnly Cookie
□ 前端路由守卫
□ 后端中间件校验
□ 角色权限检查
□ 资源归属检查
□ 登录失败不暴露存在性
□ 社交登录(如需)
□ 测试覆盖(登录 / 越权)

Common Pitfalls

  1. Storing Token in localStorage——XSS risk
  2. JWT never expires——theft remains valid forever
  3. Treating frontend guards as security——bypassed by calling the API directly
  4. Not refreshing Token——users get kicked out frequently
  5. Storing passwords in plaintext——data breach = total ruin
  6. Login exposes existence——"user does not exist" vs "wrong password"
  7. Not limiting login attempts——brute force
  8. Social login does not verify email——impersonation

Collaboration with Other Skills

上游:
  database-schema-impl → users 表

下游:
  api-frontend-integration → Token 联调
  e2e-feature-delivery → 认证是功能的一部分
  backend-engineer auth-implementation(深度)