Emeka Augustine
Emeka Augustine

Reputation: 931

The best way to pass authorization header in nextJs using Apollo client? ReferenceError: localStorage is not defined

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
}

Upvotes: 4

Views: 13788

Answers (2)

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.

Upvotes: 2

Emeka Augustine
Emeka Augustine

Reputation: 931

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;
}

Upvotes: 7

Related Questions