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

Next.js 13服务端组件如何传递Bearer Token?

问题

我通过客户端Firebase Auth获取了一个Bearer Token,并将其存储在标记为"use client"的AuthContext中。我的页面是Next.js 13的服务端组件,在该组件的getConversations异步方法中,需要向API发起fetch请求并携带此Bearer Token,请问如何获取该Token并完成请求?

提供的代码

AuthContext.tsx

// packages/webapp/src/context/AuthContext.tsx
"use client"
import React, { useEffect, useState, createContext, useContext, ReactNode } from 'react';
import { onAuthStateChanged, User, getAuth, getIdToken } from 'firebase/auth';
import firebaseApp from '@/lib/firebase';

const auth = getAuth(firebaseApp);

interface IAuthContext {
  user: User | null;
  token: string | null;
}

export const AuthContext = createContext<IAuthContext>({ user: null, token: null });

export const useAuthContext = (): IAuthContext => useContext(AuthContext);

interface IAuthContextProviderProps {
  children: ReactNode;
}

export const AuthContextProvider: React.FC<IAuthContextProviderProps> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);
  const [token, setToken] = useState<string | null>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, async (user) => {
      if (user) {
        setUser(user);
        const userToken = await getIdToken(user, true);
        setToken(userToken);
      } else {
        setUser(null);
        setToken(null);
      }
      setLoading(false);
    });

    return () => unsubscribe();
  }, []);

  return (
    <AuthContext.Provider value={{ user, token }}>
      {loading ? <div>Loading authentication...</div> : children}
    </AuthContext.Provider>
  );
};

页面组件page.tsx

// packages/webapp/src/app/(authenticated)/conversations/@list/page.tsx
import React from 'react';
import { List, Box, TextField, IconButton, Typography, Divider } from '@mui/material';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import ConversationListPreview from '../_components/ConversationListPreview';

const getConversations = async () => {
  const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/conversations?limit=3`, {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
}

export default async function ConversationListPage() {
  const conversations = await getConversations();

  return (
    <>
      <Box sx={{ width: '100%' }}>
        <Box display="flex" justifyContent="flex-start" mb={2}>
          <Typography variant="h6">Conversations</Typography>
          <IconButton color="primary">
            <AddCircleOutlineIcon />
          </IconButton>
        </Box>
        <TextField variant="outlined" fullWidth placeholder="Search..." size='small' />
      </Box>
      <List sx={{ width: '100%', overflow: 'auto' }}>
        {conversations && conversations.map((conversation, index) => (
          <>
            <ConversationListPreview key={`conversation-${conversation.id}`} conversation={conversation} />
            {(index < (conversations.length - 1)) && <Divider />}
          </>
        ))}
      </List>
    </>
  );
};

解决方案

服务端组件无法直接访问客户端Context(Context是客户端运行时状态),因此需要通过Cookie中转Token让服务端读取,以下是具体实现步骤:

步骤1:在AuthContext中将Token写入Cookie

修改AuthContext.tsx,获取Token后同步写入Cookie,设置和Firebase Token匹配的过期时间(默认1小时):

// packages/webapp/src/context/AuthContext.tsx
"use client"
import React, { useEffect, useState, createContext, useContext, ReactNode } from 'react';
import { onAuthStateChanged, User, getAuth, getIdToken } from 'firebase/auth';
import firebaseApp from '@/lib/firebase';

const auth = getAuth(firebaseApp);

interface IAuthContext {
  user: User | null;
  token: string | null;
}

export const AuthContext = createContext<IAuthContext>({ user: null, token: null });

export const useAuthContext = (): IAuthContext => useContext(AuthContext);

interface IAuthContextProviderProps {
  children: ReactNode;
}

export const AuthContextProvider: React.FC<IAuthContextProviderProps> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);
  const [token, setToken] = useState<string | null>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, async (user) => {
      if (user) {
        setUser(user);
        const userToken = await getIdToken(user, true);
        setToken(userToken);
        
        // 将Token写入Cookie,设置1小时过期
        const expires = new Date();
        expires.setTime(expires.getTime() + 60 * 60 * 1000);
        document.cookie = `firebase-token=${userToken}; expires=${expires.toUTCString()}; path=/; secure; SameSite=Strict`;
      } else {
        setUser(null);
        setToken(null);
        // 清除Cookie
        document.cookie = `firebase-token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
      }
      setLoading(false);
    });

    return () => unsubscribe();
  }, []);

  return (
    <AuthContext.Provider value={{ user, token }}>
      {loading ? <div>Loading authentication...</div> : children}
    </AuthContext.Provider>
  );
};

步骤2:服务端组件读取Cookie并发起请求

修改page.tsx,通过Next.js的cookies()方法从服务端Cookie中提取Token,再携带到请求头:

// packages/webapp/src/app/(authenticated)/conversations/@list/page.tsx
import React from 'react';
import { List, Box, TextField, IconButton, Typography, Divider } from '@mui/material';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import ConversationListPreview from '../_components/ConversationListPreview';
import { cookies } from 'next/headers';

const getConversations = async () => {
  // 从服务端Cookie获取Token
  const cookieStore = cookies();
  const token = cookieStore.get('firebase-token')?.value;

  if (!token) {
    // 无Token时返回空数组或处理未授权逻辑
    return [];
  }

  const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/conversations?limit=3`, {
    headers: {
      'Authorization': `Bearer ${token}`
    },
    // 根据需求设置缓存策略,no-store确保每次请求都获取最新数据
    cache: 'no-store'
  });

  if (!response.ok) {
    throw new Error('获取对话列表失败');
  }

  return response.json();
}

export default async function ConversationListPage() {
  const conversations = await getConversations();

  return (
    <>
      <Box sx={{ width: '100%' }}>
        <Box display="flex" justifyContent="flex-start" mb={2}>
          <Typography variant="h6">Conversations</Typography>
          <IconButton color="primary">
            <AddCircleOutlineIcon />
          </IconButton>
        </Box>
        <TextField variant="outlined" fullWidth placeholder="Search..." size='small' />
      </Box>
      <List sx={{ width: '100%', overflow: 'auto' }}>
        {conversations && conversations.map((conversation, index) => (
          <>
            <ConversationListPreview key={`conversation-${conversation.id}`} conversation={conversation} />
            {(index < (conversations.length - 1)) && <Divider />}
          </>
        ))}
      </List>
    </>
  );
};

其他可选方案

方案1:改为客户端组件

如果页面不需要服务端渲染优势,可给page.tsx添加"use client",直接通过useAuthContext获取Token:

// page.tsx顶部添加
"use client"
import { useAuthContext } from '@/context/AuthContext';
import { useState, useEffect } from 'react';

export default function ConversationListPage() {
  const { token } = useAuthContext();
  const [conversations, setConversations] = useState([]);

  useEffect(() => {
    const fetchConversations = async () => {
      if (!token) return;
      const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/conversations?limit=3`, {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      const data = await response.json();
      setConversations(data);
    };
    fetchConversations();
  }, [token]);

  // 其余代码不变
}

方案2:使用SWR/React Query

在客户端组件中用数据获取库管理请求,结合useAuthContext获取Token,兼顾缓存和状态管理。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 16:25:56