如何在Next.js(App Router)与Next-Auth中统一校验API授权令牌有效性
如何在Next.js App Router中抽离API授权校验逻辑
我正在开发一个需要认证与授权的项目,目前卡在如何从公共位置而非每个API都编写重复代码来校验API授权的问题上。
例如,我当前获取数据的代码如下:
import prisma from '@/lib/prisma'; import { NextRequest, NextResponse } from 'next/server'; import { verifyJwt } from '../../../lib/jwt'; export async function GET(request: NextRequest) { const accessToken = request.headers.get("authorization")?.split(' ')[1]; if (!accessToken || !verifyJwt(accessToken)) { return NextResponse.json( { message: "Unauthorized", }, { status: 401, } ); } try { const posts = await prisma.post.findMany(); return NextResponse.json({ data: posts }, { status: 200 }); } catch (error: any) { return NextResponse.json({ message: error.toString() }, { status: 500 }); } }
我想把这段校验代码抽离到公共位置:
const accessToken = request.headers.get("authorization")?.split(' ')[1]; if (!accessToken || !verifyJwt(accessToken)) { return NextResponse.json( { message: "Unauthorized", }, { status: 401, // token not found } ); }
附相关配置文件:
next-auth.ts
import { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; export const authConfig: NextAuthOptions = { providers: [ CredentialsProvider({ id: 'credentials', name: 'Credentials', credentials: { email: { label: "Email", type: "text", placeholder: "jsmith@email.com" }, password: { label: "Password", type: "password" } }, async authorize(credentials, req) { if (!credentials || !credentials.email || !credentials.password ) { return null; } const res = await fetch("http://localhost:3000/api/auth/token", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username: credentials?.email, password: credentials?.password, }), }); const user = await res.json(); if (user && user?.data) { return user.data; } else { return null; } } }), ], session: { strategy: "jwt", maxAge: 60 * 60 * 24 * 7, // 7 days }, pages: { signIn: '/login', error: '/login', }, callbacks: { async jwt({ token, user }) { if (user) { return { ...token, ...user }; } return token; }, async session({ session, token }) { if (token) { session.user = token; } return session; }, }, }
jwt.ts
import jwt, { JwtPayload } from "jsonwebtoken"; const secretKey = process.env.JWT_SECRET || ""; interface SignOption { expiresIn?: string | number; issuer?: string; } const ACCESS_TOKEN_SIGN_OPTION = { expiresIn: "1h", issuer: 'abcsoftware', }; const REFRESH_TOKEN_SIGN_OPTION = { expiresIn: "7d", issuer: 'abcsoftware', }; export function signJwtAccessToken( payload: JwtPayload, options: SignOption = ACCESS_TOKEN_SIGN_OPTION ) { const token = jwt.sign(payload, secretKey, options); return token; } export function signJwtRefreshToken( payload: JwtPayload, options: SignOption = REFRESH_TOKEN_SIGN_OPTION ) { const token = jwt.sign(payload, secretKey, options); return token; } // Verify authorization export function verifyJwt(token: string) { try { const decoded = jwt.verify(token, secretKey!); return decoded as JwtPayload; } catch (error: any) { console.log(error); return null; } }
使用版本:
- Next.js 13.4.6
- Next-Auth 4.22.1
- 采用Next.js的App Router
解决方案
方案1:全局API拦截(Next.js Middleware)
适合需要统一保护所有/大部分API路由的场景,在请求到达API前完成校验。
步骤1:创建middleware.ts文件
在项目根目录新建middleware.ts:
import { NextRequest, NextResponse } from 'next/server'; import { verifyJwt } from '@/lib/jwt'; export function middleware(request: NextRequest) { // 仅拦截API路由,可自定义路径规则 if (request.nextUrl.pathname.startsWith('/api/')) { const accessToken = request.headers.get('authorization')?.split(' ')[1]; if (!accessToken || !verifyJwt(accessToken)) { return NextResponse.json( { message: 'Unauthorized' }, { status: 401 } ); } // 校验通过,放行请求 return NextResponse.next(); } // 非API路由直接放行 return NextResponse.next(); } // 配置需要拦截的路径,匹配所有API路由 export const config = { matcher: '/api/:path*', };
自定义规则说明
- 如需保护特定API,修改
matcher为['/api/posts/:path*', '/api/users/:path*'] - 如需排除部分API(如登录接口),可在middleware中添加判断:
if (request.nextUrl.pathname === '/api/auth/token') { return NextResponse.next(); }
方案2:封装校验函数按需调用
适合仅需在部分API中做授权校验的场景,复用性强且灵活。
步骤1:创建公共校验函数
在@/lib/auth.ts中封装校验逻辑:
import { NextRequest, NextResponse } from 'next/server'; import { verifyJwt } from './jwt'; export function authenticateRequest(request: NextRequest): NextResponse | null { const accessToken = request.headers.get('authorization')?.split(' ')[1]; if (!accessToken || !verifyJwt(accessToken)) { return NextResponse.json( { message: 'Unauthorized' }, { status: 401 } ); } // 校验通过返回null,允许后续逻辑执行 return null; }
步骤2:在API路由中使用
修改原API代码:
import prisma from '@/lib/prisma'; import { NextRequest, NextResponse } from 'next/server'; import { authenticateRequest } from '@/lib/auth'; export async function GET(request: NextRequest) { const authError = authenticateRequest(request); if (authError) { return authError; } try { const posts = await prisma.post.findMany(); return NextResponse.json({ data: posts }, { status: 200 }); } catch (error: any) { return NextResponse.json({ message: error.toString() }, { status: 500 }); } }
方案3:结合Next-Auth会话校验
既然已配置Next-Auth,可直接使用其内置方法校验会话,无需手动处理JWT,保持认证逻辑统一。
修改API路由代码:
import prisma from '@/lib/prisma'; import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth/next'; import { authConfig } from '@/lib/next-auth'; export async function GET(request: NextRequest) { const session = await getServerSession(authConfig); if (!session) { return NextResponse.json( { message: 'Unauthorized' }, { status: 401 } ); } try { const posts = await prisma.post.findMany(); return NextResponse.json({ data: posts }, { status: 200 }); } catch (error: any) { return NextResponse.json({ message: error.toString() }, { status: 500 }); } }
内容的提问来源于stack exchange,提问作者Bablu Ahmed
相关产品推荐
相关产品推荐

