使用 Apollo 客户端在 nextJs 中传递授权标头的最佳方法? ReferenceError: localStorage 未定义

Posted

技术标签:

【中文标题】使用 Apollo 客户端在 nextJs 中传递授权标头的最佳方法? ReferenceError: localStorage 未定义【英文标题】:The best way to pass authorization header in nextJs using Apollo client? ReferenceError: localStorage is not defined 【发布时间】:2020-11-13 04:07:51 【问题描述】:

我正在尝试使用 nextJs 和 apollo 客户端从我的 graphql 服务器获取受保护的资源。我将授权令牌存储在客户端浏览器(本地存储)中并尝试从 apolloClient.Js 文件中读取令牌;但它会引发 ReferenceError(ReferenceError: localStorage is not defined)。这让我很快明白服务器端试图从后端引用 localStorage;但失败,因为它仅在客户端可用。我的问题是,解决这个问题的最佳方法是什么?我只是在我的项目中第一次使用 apollo 客户端。我花了 10 多个小时试图找出解决这个问题的方法。我在网上尝试了很多东西;得到解决方案并不幸运。这是我在 apolloClient 文件中使用的代码:

import  useMemo  from 'react'
import  ApolloClient, HttpLink, InMemoryCache  from '@apollo/client'
import  concatPagination  from '@apollo/client/utilities'
import  GQL_URL  from '../utils/api'

let apolloClient

const authToken = localStorage.getItem('authToken') || '';

function createApolloClient() 
  return new ApolloClient(
    s-s-rMode: typeof window === 'undefined',
    link: new HttpLink(
      uri: GQL_URL, // Server URL (must be absolute)
      credentials: 'include', // Additional fetch() options like `credentials` or `headers`
      headers: 
        Authorization: `JWT $authToken`
      

    ),

    
    cache: new InMemoryCache(
      typePolicies: 
        Query: 
          fields: 
            allPosts: concatPagination(),
          ,
        ,
      ,
    ),
  )


export function initializeApollo(initialState = null) 
  const _apolloClient = apolloClient ?? createApolloClient()

  // If your page has Next.js data fetching methods that use Apollo Client, the initial state
  // gets hydrated here
  if (initialState) 
    _apolloClient.cache.restore(initialState)
  
  // For SSG and s-s-r always create a new Apollo Client
  if (typeof window === 'undefined') return _apolloClient
  // Create the Apollo Client once in the client
  if (!apolloClient) apolloClient = _apolloClient

  return _apolloClient


export function useApollo(initialState) 
  const store = useMemo(() => initializeApollo(initialState), [initialState])
  return store

【问题讨论】:

您是在服务器还是客户端收到错误? 错误来自服务器! 请同时发布您的服务器代码 我指的是next js的服务端;我的 graphql 服务器没有错误 您找到解决方案了吗?我无法添加授权标头,因为在服务器中创建的 Apollo Client 实例已传递给客户端,并且您无法访问服务器中的任何 cookie 或 localstorage。 【参考方案1】:

我可以看到这个问题已经解决了。但只是部分。现在这对于进行授权的客户端查询很好,但是如果有人试图在服务器端进行授权查询,那么这将是一个问题,因为它无法访问本地存储。

所以修改这个:

//AUTH_TOKEN is the name you've set for your cookie

let apolloClient;

const httpLink = createHttpLink(
  uri: //Your URL,
);

const getAuthLink = (ctx) => 
  return setContext((_,  headers ) => 
    return 
      headers: 
        ...headers,
        authorization: iss-s-r()
          ? ctx?.req?.cookies[AUTH_TOKEN] // server-side auth token
          : getPersistedAuthToken(), /* This is your auth token from 
          localstorage */
      ,
    ;
  );
;

function createApolloClient(ctx) 
  return new ApolloClient(
    s-s-rMode: typeof window === undefined,
    link: from([getAuthLink(ctx), httpLink]),
    cache: new InMemoryCache(),
  );


export function initializeApollo( initialState = null, ctx = null ) 
  const _apolloClient = apolloClient ?? createApolloClient(ctx);
  if (initialState) 
    const existingCache = _apolloClient.extract();
    _apolloClient.cache.restore( ...existingCache, ...initialState );
  
  if (iss-s-r()) return _apolloClient;
  if (!apolloClient) apolloClient = _apolloClient;
  return _apolloClient;



getServerSide 函数如下所示:

export async function getServerSideProps(ctx) 
  const  req  = ctx;
  if (req?.cookies[AUTH_TOKEN]) 
    const apolloClient = initializeApollo( initialState: null, ctx );
    try 
      const  data  = await apolloClient.query(
        query: GET_USER_DETAILS,
      );
      // Handle what you want to do with this data / Just cache it
     catch (error) 
      const gqlError = error.graphQLErrors[0];
      if (gqlError) 
        //Handle your error cases
      
    
  
  return 
    props: ,
  ;


这样,apollo 客户端也可用于在服务器端进行授权调用。

【讨论】:

【参考方案2】:

只有当窗口对象不是“未定义”时,我才能通过访问本地存储来解决问题;因为它将在服务器端“未定义”。这会很好用,因为我们不希望服务器访问本地存储。

import  useMemo  from 'react'
import  ApolloClient, createHttpLink, InMemoryCache  from '@apollo/client';
import  setContext  from '@apollo/client/link/context';
import  GQL_URL  from '../utils/api'

let apolloClient

function createApolloClient() 
  // Declare variable to store authToken
  let token;
   
  const httpLink = createHttpLink(
    uri: GQL_URL,
    credentials: 'include',
  );

  const authLink = setContext((_,  headers ) => 
    // get the authentication token from local storage if it exists
    if (typeof window !== 'undefined') 
      token = localStorage.getItem('authToken');
    
    // return the headers to the context so httpLink can read them
    return 
      headers: 
        ...headers,
        Authorization: token ? `JWT $token` : "",
      
    
  );

  const client = new ApolloClient(
    s-s-rMode: typeof window === 'undefined',
    link: authLink.concat(httpLink),
    cache: new InMemoryCache()
  );

  return client;

【讨论】:

以上是关于使用 Apollo 客户端在 nextJs 中传递授权标头的最佳方法? ReferenceError: localStorage 未定义的主要内容,如果未能解决你的问题,请参考以下文章

使用 NextJs 将 JWT 令牌传递到 Apollo 上下文

如何在客户端 NextJS 中使用 Apollo GraphQL 订阅?

如何从 NextJS 服务器为 Apollo 客户端补充水分

在 nextJS 上配置 apollo 客户端以进行订阅的问题

Apollo 客户端未在 Nextjs Lamda 中发送 JWT 不记名令牌

无法使用 apollo-server-micro 和 NextJS 上传 1MB 以上的文件