apollo-link-refresh-token

A link to refresh auth tokens on authentication errors

Usage no npm install needed!

<script type="module">
  import apolloLinkRefreshToken from 'https://cdn.skypack.dev/apollo-link-refresh-token';
</script>

README

apollo-link-refresh-token

A link to refresh auth tokens on authentication errors

Getting started

Install the package:

yarn add apollo-link-refresh-token

Add the link to your apollo client:

Note that your implementation will likely change based on your specific parameters.

import { ApolloClient } from 'apollo-client';
import {
  getTokenRefreshLink,
  FetchNewAccessToken,
} from 'apollo-link-refresh-token';
import jwtDecode from 'jwt-decode';
import { authLink, errorLink, httpLink } from './links';

const isTokenValid = (token: string): boolean => {
  const decodedToken = jwtDecode<{ [key: string]: number }>(token);

  if (!decodedToken) {
    return false;
  }

  const now = new Date();
  return now.getTime() < decodedToken.exp * 1000;
};

const fetchNewAccessToken: FetchNewAccessToken = async refreshToken => {
  if (!process.env.REACT_APP_API_URL) {
    throw new Error(
      '.env.REACT_APP_API_URL must be set to use refresh token link'
    );
  }

  try {
    const fetchResult = await fetch(process.env.REACT_APP_API_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query: `
          mutation {
            refreshTokens(input: {
              refreshToken: "${refreshToken}"
            }) {
              accessToken
              refreshToken
              errors {
                field
                message
              }
            }
          }
        `,
      }),
    });

    const refreshResponse = await fetchResult.json();

    if (
      !refreshResponse ||
      !refreshResponse.data ||
      !refreshResponse.data.refreshTokens ||
      !refreshResponse.data.refreshTokens.accessToken
    ) {
      return undefined;
    }

    return refreshResponse.data.refreshTokens.accessToken;
  } catch (e) {
    throw new Error('Failed to fetch fresh access token');
  }
};

const refreshTokenLink = getRefreshTokenLink({
  authorizationHeaderKey: 'Authorization',
  fetchNewAccessToken,
  getAccessToken: () => localStorage.getItem('access_token'),
  getRefreshToken: () => localStorage.getItem('refresh_token'),
  isAccessTokenValid: accessToken => isTokenValid(accessToken),
  isUnauthenticatedError: graphQLError => {
    const { extensions } = graphQLError;
    if (extensions && extensions.code && extensions.code === 'UNAUTHORIZED') {
      return true;
    }
    return false;
  },
});

export const client = new ApolloClient({
  link: ApolloLink.from([authLink, refreshTokenLink, errorLink, httpLink]),
  cache,
});

Options

Option Type Default Description
authorizationHeaderKey string -- Name of the authorization header on your requests. Is used to update the headers before retrying the failed request
fetchNewAccessToken (refreshToken: string) => Promise<string | undefined> -- A function returning a promise to fetch and return the new refresh token string.
getAccessToken () => string | undefined | null -- A function to return the current access token. Is used to ensure that the user should be logged in, and to pass into isAccessTokenValid.
getRefreshToken () => string | undefined | null -- A function to return the current refreshToken. Is used to ensure that refresh is possible. It is passed to fetchNewAccessToken().
isAccessTokenValid (accessToken?: string) => boolean -- A function that takes the access token (from getAccessToken) and returns true if the access token is valid. If the token is valid, refresh won't occur.
isUnauthenticatedError (graphQLError: GraphQLError) => boolean -- A function that determines whether the error from the current operation warrants a token refresh. Usually looks for an unauthenticated code.
onFailedRefresh? (error: any) => void -- A function to handle errors when the refresh fails.
onSuccessfulRefresh? (refreshToken: string) => void -- A function ot handle successful refresh.