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).
- Avoid LocalStorage for Sensitive Tokens: Storing
tokens in
localStorageorsessionStoragemakes them vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker injects malicious JS into your site, they can easily read these storage mechanisms. - Use HttpOnly Cookies: The most secure way to handle
session tokens is storing them in an
HttpOnly,Secure, andSameSite=Strictcookie managed by your backend. Since the browser automatically attaches cookies to requests, you don’t need to manually read the token in React. - In-Memory Storage with Refresh Tokens: If you must
use tokens directly in your React code, store the short-lived access
token in React state (in-memory) and use a secure
HttpOnlycookie for the refresh token to silently request new access tokens.
2. Attach Authorization Headers with Apollo Link
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/clientNext, 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.
- Sanitize Dynamic Content: If you are rendering rich
text fetched via Apollo Client using
dangerouslySetInnerHTML, always sanitize the HTML string first using a library likeDOMPurify. - Keep Dependencies Updated: Regularly audit and update Apollo Client, React, and other npm dependencies to patch known security vulnerabilities.
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,
});