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

在NextJS与ApolloClient中如何通过Cookie实现身份认证?

解决方案:Next.js App Router + Apollo Client 跨SSR/客户端的Cookie认证实现

核心问题分析

你遇到的本质问题是:

  • SSR阶段(服务器端渲染):Apollo Client运行在服务器,无法直接访问浏览器Cookie,且绝对路径请求不会自动携带客户端Cookie
  • 客户端阶段:需要相对路径请求才能让浏览器自动附带Cookie,同时要避免导入服务器端API(如next/headers)导致的报错

下面是两种可行的优化方案,均遵循Next.js App Router和Apollo官方最佳实践:


方案一:利用makeClient上下文统一处理认证

直接在客户端组件的ApolloWrapper中,通过makeClient接收的上下文参数,在SSR时提取请求头中的Cookie,客户端则从document.cookie获取Token,统一注入Authorization头。

修改后的ApolloWrapper.ts

"use client";

import { ApolloLink, from, HttpLink } from "@apollo/client";
import {
  ApolloClient,
  ApolloNextAppProvider,
  InMemoryCache,
} from "@apollo/client-integration-nextjs";

function makeClient(context?: { headers: Headers }) {
  // 通用认证链路:区分SSR/客户端场景提取Token
  const authLink = new ApolloLink((operation, forward) => {
    let token: string | undefined;

    if (typeof window === "undefined" && context?.headers) {
      // SSR场景:从请求头的Cookie中解析token
      const cookieHeader = context.headers.get("cookie");
      if (cookieHeader) {
        token = cookieHeader.split("; ").find(c => c.startsWith("token="))?.split("=")[1];
      }
    } else {
      // 客户端场景:从浏览器Cookie中解析token
      token = document.cookie.split("; ").find(c => c.startsWith("token="))?.split("=")[1];
    }

    // 注入Authorization头
    operation.setContext(({ headers }) => ({
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : "",
      },
    }));

    return forward(operation);
  });

  const httpLink = new HttpLink({
    uri: "/graphql", // 统一使用相对路径
    credentials: "include", // 客户端请求自动携带Cookie
  });

  return new ApolloClient({
    cache: new InMemoryCache(),
    link: from([authLink, httpLink]),
  });
}

export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}

优化后的middleware.ts

import { type NextRequest, NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname === "/graphql" && request.method === "POST") {
    const headers = new Headers(request.headers);
    
    // 优先使用Apollo注入的Authorization头, fallback到Cookie解析
    if (!headers.has("Authorization")) {
      const token = request.cookies.get("token")?.value;
      if (token) {
        headers.set("Authorization", `Bearer ${token}`);
      }
    }

    // 转发到外部GraphQL服务
    return NextResponse.rewrite(new URL("http://localhost:4000/gql"), {
      request: {
        ...request,
        headers,
      },
    });
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"],
};

方案二:分离服务器/客户端Apollo配置

通过动态导入和条件导出,分别维护服务器端和客户端的Apollo链路,避免客户端代码依赖服务器API。

步骤1:创建共享配置文件apollo-shared.ts

// 无"use client"标记,可被两端导入
import { HttpLink, InMemoryCache } from "@apollo/client";

export const createHttpLink = (uri: string) => new HttpLink({
  uri,
  credentials: "include",
});

export const createCache = () => new InMemoryCache();

步骤2:服务器端Apollo配置apollo-server.ts

import { ApolloLink, from } from "@apollo/client";
import { ApolloClient, registerApolloClient } from "@apollo/client-integration-nextjs";
import { cookies } from "next/headers";
import { createCache, createHttpLink } from "./apollo-shared";

const authLink = new ApolloLink((operation, forward) => {
  operation.setContext(async () => {
    const cookieStore = await cookies();
    const token = cookieStore.get("token")?.value;
    
    return {
      headers: {
        authorization: token ? `Bearer ${token}` : "",
      },
    };
  });
  return forward(operation);
});

const httpLink = createHttpLink("http://localhost:4000/gql");

export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
  return new ApolloClient({
    cache: createCache(),
    link: from([authLink, httpLink]),
  });
});

步骤3:客户端Apollo配置ApolloWrapper.ts

"use client";

import { ApolloLink, from } from "@apollo/client";
import { ApolloClient, ApolloNextAppProvider } from "@apollo/client-integration-nextjs";
import { createCache, createHttpLink } from "./apollo-shared";

function makeClient() {
  const authLink = new ApolloLink((operation, forward) => {
    // 客户端直接从浏览器Cookie提取Token
    const token = document.cookie.split("; ").find(c => c.startsWith("token="))?.split("=")[1];
    
    operation.setContext(({ headers }) => ({
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : "",
      },
    }));

    return forward(operation);
  });

  const httpLink = createHttpLink("/graphql");

  return new ApolloClient({
    cache: createCache(),
    link: from([authLink, httpLink]),
  });
}

export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}

关键注意点

  1. SSR阶段的Cookie获取:必须通过请求头传递,服务器端无法直接访问浏览器的Cookie存储
  2. 相对路径的作用:客户端使用相对路径/graphql,结合credentials: "include",浏览器会自动附带当前域名的Cookie
  3. 避免跨环境API调用:客户端组件(标记"use client")绝对不能导入next/headers等服务器端专属API

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 12:12:34