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

Next.js 14 App Router刷新Access Token时Cookie修改报错求助

问题:Next.js 14 App Router中JWT刷新令牌时修改Cookie报错

后端通过JWT返回access token和refresh token,用户登录后存入Cookie。当access token过期尝试刷新时,触发错误:

Cookies can only be modified in a Server Action or Route Handler.

相关代码

refreshAccessToken函数

'use server'

import {cookies} from "next/headers";
import {isTokenExpiringSoon} from "@/app/utils/utils";
import {modifiedCookie} from "@/app/_actions/modifiedCookie";

export async function refreshAccessToken(refreshToken) {
    console.log("Attempting to refresh access token...");

    if (!refreshToken) {
        console.error('Refresh token not found');
        throw new Error('Refresh token not found');
    }

    const refreshResponse = await fetch(`http://localhost:8000/api/token/refresh/`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            refresh: refreshToken,
        }),
    });

    if (!refreshResponse.ok) {
        console.error('Failed to refresh token:', refreshResponse.statusText);
        throw new Error('Failed to refresh token');
    }
    console.log('working on it')

    const data = await refreshResponse.json();
    const newAccessToken = data.access;
    console.log(`newAccessToken in refresh: ${newAccessToken}`)

    modifiedCookie(newAccessToken)

    console.log("New access token set:", newAccessToken);
    return newAccessToken;
}

fetchUserData函数

export async function fetchUserData() {
    const cookieStore = cookies();
    const accessToken = cookieStore.get('access_token')?.value;

    console.log("Current access token in fetch:", accessToken);

    if (isTokenExpiringSoon(accessToken)) {
        console.log("Access token is expiring soon, refreshing...");
        try {
            const refreshToken = cookieStore.get('refresh_token')?.value;
            console.log("Refresh Token:", refreshToken);
            await refreshAccessToken(refreshToken);
        } catch (error) {
            console.error('Error refreshing access token:', error);
            throw new Error('Unable to refresh access token');
        }
    }

    const newAccessToken = cookieStore.get('access_token')?.value;
    console.log("New access token to use:", newAccessToken);

    const response = await fetch(`http://localhost:8000/api/user/user-profile/`, {
        method: 'GET',
        headers: {
            'Authorization': `Bearer ${newAccessToken}`,
            'Content-Type': 'application/json',
        },
    });

    if (!response.ok) {
        console.error('Failed to fetch user profile:', response.statusText);
        throw new Error('Failed to fetch user profile');
    }

    const user = await response.json();
    console.log("Fetched user data:", user);
    return user;
}

错误日志

Current access token in fetch: ey...
Access token is expiring soon, refreshing...
Refresh Token: ey...
Attempting to refresh access token...
working on it
newAccessToken in refresh: ey...
New access token set: ey...
New access token to use: ey...
tA [Error]: Cookies can only be modified in a Server Action or Route Handler.
at Proxy.callable (/home/comiser/Desktop/tasteproject/frontend/tastecoffee/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:13455)
at modifiedCookie (webpack-internal:///(rsc)/./app/_actions/modifiedCookie.js:14:61)
at refreshAccessToken (webpack-internal:///(rsc)/./app/_actions/actions.js:41:80)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async fetchUserData (webpack-internal:///(rsc)/./app/_actions/actions.js:54:13)
at async RootLayout (webpack-internal:///(rsc)/./app/layout.js:38:16)
Failed to fetch user profile: Unauthorized
Error fetching user profile: Failed to fetch user profile

错误原因

  1. fetchUserData未标记为Server Action:虽然refreshAccessToken加了'use server',但调用它的fetchUserData没有该标识,导致执行上下文不允许修改Cookie。
  2. Cookie读取时机错误:fetchUserData一开始就获取了cookieStore,刷新token后直接读取旧的cookieStore无法拿到新token。

解决方案

1. 给fetchUserData添加Server Action标识

在函数顶部加上'use server',确保整个函数运行在允许修改Cookie的Server Action上下文:

'use server'

export async function fetchUserData() {
    // 原有代码
}

2. 刷新后重新获取Cookie

调用refreshAccessToken后,重新获取Cookie实例以拿到最新的access token:

// 原代码
await refreshAccessToken(refreshToken);
// 修改为:
await refreshAccessToken(refreshToken);
const updatedCookieStore = cookies();
const newAccessToken = updatedCookieStore.get('access_token')?.value;

3. 确保modifiedCookie在Server上下文运行

如果modifiedCookie是单独文件,需在顶部添加'use server':

// modifiedCookie.js
'use server'

import {cookies} from "next/headers";

export async function modifiedCookie(newAccessToken) {
    cookies().set('access_token', newAccessToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        maxAge: 60 * 15,
        path: '/'
    });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 13:36:07