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

独立后端下NextJS认证:如何请求令牌保护的路由

NextJS前后端认证请求问题解答

我查了大量资料,网上相关指南大多只展示把令牌存入Cookie和会话刷新的示例,但我最关心(也最棘手)的是后续怎么向令牌保护的路由发起HTTP请求。

项目涉及主体

  • 远程后端服务器(支持通过Authorization头或user_token Cookie认证)
  • NextJS(服务端)
  • NextJS(客户端)

期望认证流程

  1. 用户填写邮箱密码表单,通过Server Action从NextJS客户端发请求到NextJS服务端;
  2. NextJS服务端向远程后端发起请求,状态正常的话,通过{ cookies } from 'next/headers'设置http-only的"user_token" Cookie并返回响应给客户端;
  3. 之后要实现从NextJS服务端和客户端都能向后端的令牌保护路由发起请求。

问题解答

a) NextJS服务端fetch的credentials问题

NextJS服务端环境中,fetch的credentials: "include"不会自动携带通过cookies().set设置的Cookie——因为服务端的Cookie存储和浏览器的Cookie池完全隔离。你必须手动从cookies()中获取令牌,再通过请求头或Cookie字段传递给后端。

推荐方案(用Authorization头):

export const profileGet = async () => { 
  const token = cookies().get("user_token")?.value;
  return fetch(`${CONFIG.BASE_URL}/profile`, { 
    method: "GET",
    headers: {
      ...DEFAULT_HEADERS,
      Authorization: `Bearer ${token}`
    },
  });
};

若需用Cookie传递:

export const profileGet = async () => { 
  const token = cookies().get("user_token")?.value;
  return fetch(`${CONFIG.BASE_URL}/profile`, { 
    method: "GET",
    headers: {
      ...DEFAULT_HEADERS,
      Cookie: `user_token=${token}`
    },
  });
};

credentials: "include"在服务端fetch中仅会携带当前用户请求传入的Cookie(即浏览器发往NextJS服务端的Cookie),而非你通过cookies().set设置的Cookie,因此无法让它直接生效。

b) 生产环境同域下的客户端Cookie携带

如果NextJS与后端同域(如均为example.com),只要Cookie配置满足以下条件,客户端请求会自动携带user_token:

  • Cookie的domain设为.example.com(或不设置,默认绑定当前域名)
  • path设为/
  • sameSite设为lax或none(跨子域时none需配合secure: true)
  • 生产环境下secure必须设为true(HTTPS环境才会携带Cookie)

你的storeToCookies函数中secure: false需要在生产环境改为true,否则浏览器不会在HTTPS请求中携带该Cookie。

c) 开发模式下跨域名Cookie共享

开发模式下NextJS运行在localhost:3000,后端在其他域名(如localhost:8000),默认Cookie无法跨域共享,有两种解决方案:

方案1:反向代理(推荐)

在next.config.js中配置反向代理,将后端请求映射到localhost:3000的子路径,使客户端请求与NextJS同域:

// next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:8000/:path*', // 替换为你的后端地址
      },
    ];
  },
};

随后将客户端apisauce的baseURL改为/api,请求会自动代理到后端,Cookie也会被自动携带。

方案2:Cookie与CORS配置调整

若不想用代理,需修改Cookie配置并调整后端CORS:

  • 更新storeToCookies的Cookie参数:
cookies().set(
  key,
  typeof value === "string" ? value : JSON.stringify(value),
  {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'none',
    path: '/',
    domain: 'localhost',
  }
);
  • 后端CORS配置需设置:
    • Access-Control-Allow-Credentials: true
    • Access-Control-Allow-Origin: http://localhost:3000(不能设为*)

修改后,开发模式下Cookie可同时作用于localhost:3000和后端域名。


代码示例

Login Server Action(简化代码)

export const loginAction = async (state: unknown, formData: unknown): Promise<LoginFormState> => {
  if (!(formData instanceof FormData)) 
    return {
      message: "Invalid payload",
      ok: false
    };

  // Validate form fields
  const validatedFields = LoginFormSchema.safeParse({
    email: formData.get('email'),
    password: formData.get('password'),
  })
 
  // If any form fields are invalid, return early
  if (!validatedFields.success) {
    console.log('validation error');
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    } as LoginFormState
  }

  const loginResponse = await loginPost({
    ...validatedFields.data
  });

  if (!classicNextApiResponseValidator(loginResponse)) {
    const error = await requestErrorFromNextResponse(loginResponse);
    return {
      message: error.description,
      ok: false
    }
  }

  const responseData: T.LoginResponse = await loginResponse.json();
       
  await storeToCookies("user_token", responseData.accessToken);

  if (responseData.emailVerified) {
    await redirectAction("/dashboard", {});
  } else {
    await redirectAction("/emailVerification", {});
  }
 
  return {
    message: "successfully",
    ok: true
  }
};

storeToCookies函数

"use server";

import { cookies } from 'next/headers';
import { CookiesKey } from './types';

export const storeToCookies = async <T = any>(key: CookiesKey, value: T, options: {} = {}) => {
  if (typeof key === "string")
    cookies().set(
      key,
      typeof value === "string" ? value : JSON.stringify(value),
      {
        httpOnly: true,
        secure: false,
        sameSite: 'lax',
        path: '/',
      }
    );
};

NextJS服务端请求受保护路由的尝试(credentials: "include"无效,手动传Cookie有效)

export const profileGet = async () => { 
  return fetch(`${CONFIG.BASE_URL}/profile`, { 
    method: "GET",
    credentials: "include",
    headers: DEFAULT_HEADERS,
  });
};

客户端请求的apisauce实例

const sauce = create({
  withCredentials: true,
  baseURL: CONFIG.BASE_URL,
  headers: {
    Accept: 'application/json',
  }
});

内容的提问来源于stack exchange,提问作者Evok

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:42:05