如何从中间件更新 NextJS 中的 cookie

问题描述 投票:0回答:1

我有一个使用外部 Oauth API 的 NextJS 14 应用程序。成功登录后,我使用访问和刷新令牌设置了一些 cookie,这样我就可以在调用后端(Spring)时使用这些令牌。

当我想从中间件刷新访问令牌时遇到问题,我无法设置 cookie,因为出现以下错误:

[错误]:Cookie 只能在服务器操作或路由处理程序中修改。了解更多:https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options

如果我尝试在中间件中覆盖 reqquest.cookies 或 response.cookies ,那么调用 cookies().get() 时获得的 cookie 似乎具有旧值。

如果我设置服务器操作来更新 cookie 并从中间件导入它,我会收到相同的错误。

我需要能够从一个集中的地方更新令牌,这样我就不必检查过期的令牌并更新它们,以防它们在每次调用后端时都过期了。

我没有任何路由处理程序,因为我直接从客户端组件调用后端。

在 /src/lib/auth.ts 中我有我的凭据提供者:

import { cookies } from "next/headers";

[... ommitted irrelevant parts ...]

export const authOptions: NextAuthOptions = {
  session: {
    strategy: "jwt",
  },
  providers: [
    CredentialsProvider({
      name: "credentials",
      credentials: {
        username: { label: "Username", type: "text" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        const { username, password } = credentials as {
          username: string;
          password: string;
        };

        const formData = new URLSearchParams();
        formData.append("email", encodeURIComponent(username));
        formData.append("password", encodeURIComponent(password));

        const res = await fetch(loginPath, {
          method: "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
          },
          body: formData.toString(),
        });
        const receivedToken = await res.json();
        console.log("Received token: " + receivedToken);
        if (res.ok && receivedToken) {
          const user = receivedToken;
          const parsed = parsePayload(receivedToken.access_token);
          user.name = parsed.name;
          cookies().set({
            name: "gat",
            value: receivedToken.access_token,
            httpOnly: true,
          });
          cookies().set({
            name: "grt",
            value: receivedToken.refresh_token,
            httpOnly: true,
          });
          cookies().set({
            name: "exp",
            value: parsed.exp,
            httpOnly: true,
          });

          return user;
        } else {
          return null;
        }
      },
    }),
  ],
  callbacks: {
    jwt: async ({ token, user }: { token: any; user: any }) => {
      if (user) token = user as unknown as { [key: string]: any };
      return token;
    },
    session: async ({ session, token }: { session: any; token: any }) => {
      session.user = { ...token };      
      return session;
    },
  },
  pages: {
    signIn: "/login",
  },
};

我还尝试检查令牌是否过期并在此处更新会话回调中的cookie,但我得到了相同的错误。

我尝试按如下方式覆盖中间件中的cookie,但正如我之前所说,当我稍后使用 cookies().get() 访问这些值时,我得到的是旧值,而不是新值:

export async function middleware(req) {

[... ommitted irrelevant things ...]

let expTime = req.cookies.get("exp")?.value;
  let grtValue = req.cookies.get("grt")?.value;
  if (grtValue && expTime && new Date(+expTime * 1000) < Date.now()) {
    let response = NextResponse.next();
    updateToken(req, response, grtValue);
    return response;
  }
}

export async function updateToken(req, resp, grtValue) { 
    try {
      const res = await fetch(refreshPath, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(new Refresh(grtValue)),
      });
      const receivedToken = await res.json();
      console.log("Refreshed access token: " + receivedToken);
      if (res.ok && receivedToken) {
        let parsedPayload = parsePayload(receivedToken.access_token);
        req.cookies.set({
          name: "gat",
          value: receivedToken.access_token,
          httpOnly: true,
        });
        resp.cookies.set({
          name: "gat",
          value: receivedToken.access_token,
          httpOnly: true,
        });
        req.cookies.set({
          name: "grt",
          value: receivedToken.refresh_token,
          httpOnly: true,
        });
        resp.cookies.set({
          name: "grt",
          value: receivedToken.refresh_token,
          httpOnly: true,
        });
        
        req.cookies.set({
          name: "exp",
          value: parsedPayload.exp,
          httpOnly: true,
        });
        resp.cookies.set({
          name: "exp",
          value: parsedPayload.exp,
          httpOnly: true,
        });
      } else {
        console.log("Error while refreshing token " + res.statusText);
      }
    } catch (error) {
      console.log(error);
    }
  }
reactjs next.js cookies oauth refresh-token
1个回答
0
投票

您的主要挑战是在成功登录 oAuth 后更新存储在 cookie 中的访问和刷新令牌,特别是当这些令牌过期时。出于安全和架构原因,Next.js 将 cookie 修改限制在特定区域,这就是您在尝试在服务器操作或路由处理程序之外更新 cookie 时遇到错误的原因。

为了解决这个问题,您需要一种集中的方式来管理令牌刷新而不违反 Next.js 的限制。该策略涉及使用专用于刷新令牌和更新 cookie 的 API 路由,您可以根据需要从客户端或中间件调用。

创建用于刷新令牌的 API 路由:此路由将处理与 OAuth 提供商的通信以刷新令牌,并且能够设置 cookie。

在pages/api目录下创建一个文件,例如refresh-token.js。

在此文件中,编写调用 OAuth API 的逻辑,以使用刷新令牌刷新访问令牌。

客户端Token刷新逻辑: 在调用后端之前,请检查访问令牌是否需要刷新。如果是这样,请调用您刚刚创建的 /api/refresh-token 端点来刷新令牌。

如果您使用 Axios 进行 HTTP 请求,这可以作为实用函数或使用拦截器来实现。

中间件调整:由于直接在中间件中修改 cookie 面临限制,因此:检测令牌是否需要刷新 中间件。 不要尝试直接在中间件中刷新令牌,而是将请求重定向到 API 路由 (/api/refresh-token) 或返回指示客户端应刷新令牌的响应。 集中式令牌管理:通过为令牌刷新提供专用 API 路由,您可以集中此逻辑,从而更轻松地管理整个应用程序。这种方法尊重 Next.js 的架构限制,同时为令牌管理提供无缝体验。

// pages/api/refresh-token.js
export default async function handler(req, res) {
  // Logic to refresh token and handle errors
  // Use res.setHeader('Set-Cookie', ...) to set new tokens
}



if (tokenNeedsRefresh) {
  // Redirect to refresh-token API route
}

© www.soinside.com 2019 - 2024. All rights reserved.