You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:24:53