access and refresh token with Apollo/graphQL/React

Viewed 582

after a long search, I made up this solution for my app, when the access token is expired. The difference from the other ones is that I have to use an external service that provides me the access and refresh token when logging with my google account. Then, when the access token has expired, I need to retrieve the refresh token, send it to the service that provides me new access and refresh token. I have seen implementations where you do not wait to get the 401 error but you calculate the time before the token expires and send the request and others where you handle the pending requests. This will be an internal app used to manage our assets so I do not expect many concurrent queries/requests from a single user so I avoided the part where the pending requests are handled. Unfortunately at the moment, I cannot try it because the service is down up to next year, but I would like to ask if it is correct or if I need to add more logic

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { AppProvider } from "./context";
import {
  ApolloProvider,
  ApolloClient,
  ApolloLink,
  createHttpLink,
  InMemoryCache,
  from,
  fromPromise,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { onError } from "@apollo/client/link/error";
const axios = require("axios").default;

const httpLink = createHttpLink({
  uri: "http://localhost:4000",
});

const authLink = new ApolloLink((operation, forward) => {
  const accessToken = localStorage.getItem("accessToken");

  operation.setContext(({ headers }) => ({
    headers: {
      ...headers,
      authorization: accessToken ? `Bearer ${accessToken}` : "",
    },
  }));

  return forward(operation);
});

const getNewToken = async () => {
  try {
    const { data } = await axios.post(
      "https://xxx/api/v2/refresh",
      { token: localStorage.getItem("refreshToken") }
    );
    localStorage.setItem("refreshToken", data.refresh_token);
    return data.access_token;
  } catch (error) {
    console.log(error);
  }
};

const errorLink = onError(
  ({ graphQLErrors, networkError, operation, forward }) => {
    if (graphQLErrors) {
      for (let err of graphQLErrors) {
        switch (err.statusCode) {
          case 401:
            return fromPromise(
              getNewToken().catch((error) => {
                // Handle token refresh errors e.g clear stored tokens, redirect to login
                return;
              })
            )
              .filter((value) => Boolean(value))
              .flatMap((accessToken) => {
                const oldHeaders = operation.getContext().headers;
                // modify the operation context with a new token
                operation.setContext({
                  headers: {
                    ...oldHeaders,
                    authorization: `Bearer ${accessToken}`,
                  },
                });

                // retry the request, returning the new observable
                return forward(operation);
              });
        }
      }
    }
  }
);

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

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <AppProvider>
        <App />
      </AppProvider>
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById("root")
);
0 Answers
Related