admin管理员组

文章数量:1279112

I am trying to fetch protected resource from my graphql server using nextJs and apollo client. I stored the authorization token in the client browser (localstorage) and try to read the token from apolloClient.Js file; but it throws a ReferenceError (ReferenceError: localStorage is not defined). This makes me to understand quickly that the server side was trying to reference localStorage from the backend; but fails because it is only available in the client. My question is, what is the best way to solve this issue? I am just using apollo client for the first time in my project. I have spent more than 10 hours trying to figure out the solution to this problem. I have tried so many things on web; not lucky to get the solution. Here is the code am using in apolloClient file:

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({
    ssrMode: 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 SSR 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
}

I am trying to fetch protected resource from my graphql server using nextJs and apollo client. I stored the authorization token in the client browser (localstorage) and try to read the token from apolloClient.Js file; but it throws a ReferenceError (ReferenceError: localStorage is not defined). This makes me to understand quickly that the server side was trying to reference localStorage from the backend; but fails because it is only available in the client. My question is, what is the best way to solve this issue? I am just using apollo client for the first time in my project. I have spent more than 10 hours trying to figure out the solution to this problem. I have tried so many things on web; not lucky to get the solution. Here is the code am using in apolloClient file:

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({
    ssrMode: 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 SSR 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
}
Share Improve this question edited Jul 23, 2020 at 12:34 Emeka Augustine asked Jul 23, 2020 at 12:19 Emeka AugustineEmeka Augustine 9311 gold badge12 silver badges17 bronze badges 6
  • 1 Are you getting the error on the server or on the client? – Kisinga Commented Jul 23, 2020 at 14:44
  • The error is from the server! – Emeka Augustine Commented Jul 23, 2020 at 15:50
  • please post your server code as well – Kisinga Commented Jul 23, 2020 at 15:50
  • I mean the server side of next js; no error from my graphql server – Emeka Augustine Commented Jul 23, 2020 at 16:49
  • Did you find a solution to this? I haven't been able to add authorization header because the Apollo Client instance created in the server is passed to the client, and you can't access any of the cookies or localstorage in the server. – Danny Commented Aug 4, 2020 at 7:48
 |  Show 1 more ment

2 Answers 2

Reset to default 7

I was able to solve the problem by accessing the local storage only when the window object is not 'undefined'; since it will be 'undefined' in the server side. This will work well because we don't want the server to access local storage.

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({
    ssrMode: typeof window === 'undefined',
    link: authLink.concat(httpLink),
    cache: new InMemoryCache()
  });

  return client;
}

I can see this issue has been solved. But only partially. Right now this is fine for making authorized client-side queries but if someone is trying to make an authorized query on the server-side, then this would be an issue as it doesn't have access to local storage.

So modifying this :

//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: isSSR()
          ? ctx?.req?.cookies[AUTH_TOKEN] // server-side auth token
          : getPersistedAuthToken(), /* This is your auth token from 
          localstorage */
      },
    };
  });
};

function createApolloClient(ctx) {
  return new ApolloClient({
    ssrMode: 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 (isSSR()) return _apolloClient;
  if (!apolloClient) apolloClient = _apolloClient;
  return _apolloClient;
}


The getServerSide function would look like this:

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: {},
  };
}

This way the apollo client can be used to make authorized calls on the server-side as well.

本文标签: