newsiberian / apollo-link-token-refresh

Apollo Link that performs access tokens (JWT) renew
MIT License
337 stars 45 forks source link

How exactly shoud I get the new acces token in handleResponse function? #29

Open sk8Guerra opened 4 years ago

sk8Guerra commented 4 years ago

I'm trying to extract the new token that is generated when the old one has expired. But when I try to access it, it is undefined:

handleResponse: (operation, accessTokenField) => response => {
    console.log({ ac: response.refreshToken }); // -> {ac: undefined}
},

how should I extract it?

sturoid commented 4 years ago

Here is my implementation that has been working great. Hopefully it helps. This is how I create the apollo "client". I then pass that to ApolloProvider.

import jwtDecode from 'jwt-decode';
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import { TokenRefreshLink } from 'apollo-link-token-refresh';

const httpLink = new HttpLink({ uri: `${process.env.REACT_APP_API_URL}/graphql` });

const authLink = setContext((_, { headers }) => {
  // Get the authentication token from local storage if it exists
  const token = localStorage.getItem('sict');
  // Return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : ''
    }
  };
});

const refreshTokenLink = new TokenRefreshLink({
  accessTokenField: 'tokens',
  isTokenValidOrUndefined: () => {
    const token = localStorage.getItem('sict');

    if (!token) return true;

    try {
      const { exp } = jwtDecode(token);
      const expires = new Date(exp * 1000);
      if (Date.now() >= expires) return false;
      return true;
    } catch {
      return false;
    }
  },
  fetchAccessToken: () => {
    return fetch(`${process.env.REACT_APP_API_URL}/refresh_token`, {
      method: 'GET',
      headers: {
        authorization: `Bearer ${localStorage.getItem('sict')}`,
        sicrt: localStorage.getItem('sicrt')
      }
    });
  },
  handleFetch: (newTokens) => {
    const { token, refreshToken } = newTokens;

    if (!token || !refreshToken) {
      localStorage.removeItem('sict');
      return localStorage.removeItem('sicrt');
    }

    localStorage.setItem('sict', token);
    return localStorage.setItem('sicrt', refreshToken);
  },
  handleError: () => {
    localStorage.removeItem('sict');
    localStorage.removeItem('sicrt');
    return window.location.reload();
  }
});

const client = new ApolloClient({
  link: ApolloLink.from([
    refreshTokenLink,
    // eslint-disable-next-line
    onError(({ graphQLErrors, networkError }) => {
      // TODO: Custom error handler here.
      // eslint-disable-next-line
      // console.log('apollo.js onError graphQLErrors: ', graphQLErrors);
      // eslint-disable-next-line
      // console.log('apollo.js onError networkError: ', networkError);
    }),
    authLink,
    httpLink
  ]),
  cache: new InMemoryCache({})
});

export default client;
themathiou commented 3 years ago

@sturoid what url do you use for the refresh_token? what is ${process.env.REACT_APP_API_URL}/refresh_token in your case? Do you have a custom implementation for refresh token? What server do you use?

MarttiR commented 2 years ago

The example in https://github.com/newsiberian/apollo-link-token-refresh/issues/29#issuecomment-700336766 has a security issue. Storing JWT tokens in localStorage can make a public app vulnerable to credential theft by XSS.

See: https://auth0.com/docs/secure/security-guidance/data-security/token-storage#don-t-store-tokens-in-local-storage