How to Secure Apollo Client in React

Securing your frontend data layer is critical when building modern web applications. This article provides a straightforward guide on how to secure Apollo Client in React, focusing on safe token storage, implementing secure authorization headers, handling token expiration, and preventing common security vulnerabilities like Cross-Site Scripting (XSS).

1. Store Authentication Tokens Securely

The first step in securing Apollo Client is deciding where to store your authentication tokens (like JWTs).

To send the access token with every GraphQL request, use @apollo/client/link/context to dynamically set HTTP headers. This ensures that even if the token changes or is refreshed, Apollo Client always sends the most up-to-date credentials.

First, install the context link package:

npm install @apollo/client

Next, configure your Apollo Client instance:

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: process.env.REACT_APP_GRAPHQL_API_URL,
});

const authLink = setContext((_, { headers }) => {
  // Retrieve the token from in-memory storage or a secure state manager
  const token = getAccessToken(); 

  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  };
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});

3. Handle Token Expiration and Authentication Errors

When an access token expires, your backend will return a 401 Unauthorized error. You should intercept this error in Apollo Client to trigger a token refresh or redirect the user to the login page.

You can achieve this using @apollo/client/link/error:

import { onError } from '@apollo/client/link/error';

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    for (let err of graphQLErrors) {
      if (err.extensions?.code === 'UNAUTHENTICATED') {
        // Perform action to refresh token or log out the user
        logoutUser();
      }
    }
  }
  if (networkError && networkError.statusCode === 401) {
    logoutUser();
  }
});

// Combine errorLink, authLink, and httpLink
const client = new ApolloClient({
  link: errorLink.concat(authLink).concat(httpLink),
  cache: new InMemoryCache(),
});

4. Protect Against Cross-Site Scripting (XSS) in React

While React inherently protects against basic XSS by escaping values in JSX, vulnerabilities can still occur.

5. Use Environment Variables for API Endpoints

Never hardcode your GraphQL endpoint in your React source code. Always use environment variables to configure your Apollo Client URI based on the development, staging, or production environment.

const httpLink = createHttpLink({
  uri: process.env.REACT_APP_GRAPHQL_URI,
});