shopify-app-template-node icon indicating copy to clipboard operation
shopify-app-template-node copied to clipboard

Apollo Client sending requests with expired session tokens

Open RMartires opened this issue 3 years ago • 1 comments

Issue summary

So i'm using the boilerplate code in the repo for ApolloClient

so the main code is

function MyProvider({ children }) {
  const app = useAppBridge();

  const client = new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({
      credentials: "include",
      fetch: userLoggedInFetch(app),
    }),
  });

  return <ApolloProvider client={client}>{children}</ApolloProvider>;
}

export function userLoggedInFetch(app) {
  return async (uri, options) => {
    const fetchFunction = authenticatedFetch(app);
    const response = await fetchFunction(uri, options);

    if (
      response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1"
    ) {
      const authUrlHeader = response.headers.get(
        "X-Shopify-API-Request-Failure-Reauthorize-Url"
      );

      const redirect = Redirect.create(app);
      redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/auth`);
      return null;
    }

    return response;
  };
}

Now when i run any query it fails giving an error of token expired, weird since i'm using the function userLoggedInFetch() to make a api call to express using

async function createDraftOrderJob(app, data) {
  const fetch = await userLoggedInFetch(app);
  return fetch("api/createDraftOrderJob", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify(data),
  });
}

now this works but the apollo client does not, not sure what i'm doing wrong

RMartires avatar Jun 06 '22 12:06 RMartires

likely the app variable you're passing in lacks the required context. Also you're returning the entire fetch object not the response. Also userLoggedInFetch isn't async by default. Try something like

//MySuperCoolComponent.js
import { useAppBridge } from "@shopify/app-bridge-react";
import { userLoggedInFetch } from "../App";

export default function MySuperCoolComponent(){
  const app = useAppBridge();
  const fetch = userLoggedInFetch(app);
  async function createDraftOrderJob(data) {
      fetch("api/createDraftOrderJob", {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
        }
      }
  }
}

But I think what's most important here is the key understanding what this code is doing, if you understand that you can do this however you want, ideally leveraging axios or react-query.

Shopify requires a Bearer token for request authentication in the form of a JSON Web Token (JWT).

All userLoggedInFetch is doing is adding an /auth redirect on failure and calling authenticatedFetch from @shopify/app-bridge-utils, and all that function is doing essentially is making use of the getSessionToken function and attaching that Bearer token to all requests. So at the end of the day all you need is that JWT header on the request. Here is an example of a custom implementation using fetch and axios

fetch

const authFetch = async (url, options = {}) => {
  const newToken = await getSessionToken(window.app);// stored in window variable on mount
  const updateOptions = (newOptions) => {
    const update = { ...newOptions };
    update.headers = {
      ...update.headers,
      Authorization: `Bearer ${newToken}`,
    };
    return update;
  };
  return fetch(url, updateOptions(options));
};

axios

import axios from "axios";
import { getSessionToken } from "@shopify/app-bridge-utils";

// This client is used to make authenticated requests from the app frontend, to the server.

const createServerClient = (useAPIUrl = true) => {
  let baseURL = "/api/v1";
  if (!useAPIUrl) {
    baseURL = "";
  }

  const app = window.app; // stored in window variable on mount
  const serverClient = axios.create({
    baseURL,
    timeout: 100000,
  });

  serverClient.interceptors.request.use(function (config) {
    return getSessionToken(app) // requires a Shopify App Bridge instance
      .then((token) => {
        // Append your request headers with an authenticated token
        config.headers["Authorization"] = `Bearer ${token}`;
        return config;
      });
  });

  return serverClient;
};

export default createServerClient;

Hope that helps, close out the issue if it does. Thanks

Michael-Gibbons avatar Jun 11 '22 02:06 Michael-Gibbons

This issue is stale because it has been open for 90 days with no activity. It will be closed if no further action occurs in 14 days.

github-actions[bot] avatar Sep 28 '22 02:09 github-actions[bot]

We are closing this issue because it has been inactive for a few months. This probably means that it is not reproducible or it has been fixed in a newer version. If it’s an enhancement and hasn’t been taken on since it was submitted, then it seems other issues have taken priority.

If you still encounter this issue with the latest stable version, please reopen using the issue template. You can also contribute directly by submitting a pull request– see the CONTRIBUTING.md file for guidelines

Thank you!

github-actions[bot] avatar Oct 13 '22 02:10 github-actions[bot]