Solved

Shopify.Utils.loadCurrentSession returns undefined

noahfromfolds
Shopify Partner
17 2 6

I've been trying to learn cookieless sessions recently. I usually work on the front end so I'm a bit new to the backend.

I'm using the default generated CLI code but Shopify.Utils.loadCurrentSession has been returning undefined. Any help would be greatly appreciated 🙂

This is my full server.js code:

 

import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "@shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
const fetch = require("node-fetch");

const mongoose = require("mongoose");
const User = require("./Models/User");
const bodyParser = require("koa-bodyparser");

dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
});
const handle = app.getRequestHandler();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES.split(","),
  HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};

app.prepare().then(async () => {
  const server = new Koa();
  const router = new Router();

  server.use(bodyParser());

  server.keys = [Shopify.Context.API_SECRET_KEY];
  server.use(
    createShopifyAuth({
      accessMode: "offline",
      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shop, accessToken, scope } = ctx.state.shopify;
        ACTIVE_SHOPIFY_SHOPS[shop] = scope;

        const response = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/webhooks",
          topic: "APP_UNINSTALLED",
          webhookHandler: async (topic, shop, body) =>
            delete ACTIVE_SHOPIFY_SHOPS[shop],
        });

        if (!response.success) {
          console.log(
            `Failed to register APP_UNINSTALLED webhook: ${response.result}`
          );
        }

        // Redirect to app with shop parameter upon auth
        ctx.redirect(`/?shop=${shop}`);
      },
    })
  );

  const handleRequest = async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  };

  router.get("/", async (ctx) => {
    const shop = ctx.query.shop;

    // This shop hasn't been seen yet, go through OAuth to create a session
    if (ACTIVE_SHOPIFY_SHOPS[shop] == undefined) {
      ctx.redirect(`/auth?shop=${shop}`);
    } else {
      await handleRequest(ctx);
    }
  });

  router.post("/webhooks", async (ctx) => {
    try {
      await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
      console.log(`Webhook processed, returned status code 200`);
    } catch (error) {
      console.log(`Failed to process webhook: ${error}`);
    }
  });

  router.get("/api/themes", async (ctx) => {
    console.log("before load session");
    const session = await Shopify.Utils.loadCurrentSession(
      ctx.req,
      ctx.res,
      false
    );
    console.log("session", session); // <= RETURNS UNDEFINED
  });

  router.post(
    "/graphql",
    verifyRequest({ returnHeader: true }),
    async (ctx, next) => {
      await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
    }
  );

  router.get("(/_next/static/.*)", handleRequest); // Static content is clear
  router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
  router.get("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions

  server.use(router.allowedMethods());
  server.use(router.routes());

  mongoose.connect(
    process.env.DB_CONNECTION,
    { useNewUrlParser: true, useUnifiedTopology: true },
    () => {
      console.log("connected to DB");
    }
  );

  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

 

 

The part that includes the Shopify.Utils.loadCurrentSession that returns undefined is this:

 

  router.get("/api/themes", async (ctx) => {
    console.log("before load session");
    const session = await Shopify.Utils.loadCurrentSession(
      ctx.req,
      ctx.res,
      false
    );
    console.log("session", session); // <= RETURNS UNDEFINED
  });

 

 

I would greatly appreciate any help.

Thank you!

 

Founder / CEO of Folds Page Builder | Available for free at: https://apps.shopify.com/folds
Accepted Solution (1)

noahfromfolds
Shopify Partner
17 2 6

This is an accepted solution.

Alright! I finally figured it out. For some reason I was forgetting to use Authenticated Fetch and session tokens on the front end.

I used https://shopify.dev/tutorials/use-session-tokens-with-axios and another Shopify forum post (I accidentally closed the tab so I don't have a link).

 

function MyProvider(props) {
  const app = useAppBridge();

  // Create axios instance for authenticated request
  const authAxios = axios.create();

  authAxios.interceptors.request.use(function (config) {
    return getSessionToken(app).then((token) => {
      // append your request headers with an authenticated token
      config.headers["Authorization"] = `Bearer ${token}`;
      return config;
    });
  });

  const client = new ApolloClient({
    fetch: userLoggedInFetch(app),
    fetchOptions: {
      credentials: "include",
    },
  });

  const Component = props.Component;

  return (
    <ApolloProvider client={client}>
      <Component {...props} authAxios={authAxios} />
    </ApolloProvider>
  );
}

class MyApp extends App {
  render() {
    const { Component, pageProps, shopOrigin } = this.props;
    return (
      <AppProvider i18n={translations}>
        <Provider
          config={{
            apiKey: API_KEY,
            shopOrigin: shopOrigin,
            forceRedirect: true,
          }}
        >
          <RoutePropagator />
          <MyProvider Component={Component} {...pageProps} />
        </Provider>
      </AppProvider>
    );
  }

 

then you can use props.authAxios to access it for any axios call!

Founder / CEO of Folds Page Builder | Available for free at: https://apps.shopify.com/folds

View solution in original post

Replies 5 (5)

noahfromfolds
Shopify Partner
17 2 6

This is an accepted solution.

Alright! I finally figured it out. For some reason I was forgetting to use Authenticated Fetch and session tokens on the front end.

I used https://shopify.dev/tutorials/use-session-tokens-with-axios and another Shopify forum post (I accidentally closed the tab so I don't have a link).

 

function MyProvider(props) {
  const app = useAppBridge();

  // Create axios instance for authenticated request
  const authAxios = axios.create();

  authAxios.interceptors.request.use(function (config) {
    return getSessionToken(app).then((token) => {
      // append your request headers with an authenticated token
      config.headers["Authorization"] = `Bearer ${token}`;
      return config;
    });
  });

  const client = new ApolloClient({
    fetch: userLoggedInFetch(app),
    fetchOptions: {
      credentials: "include",
    },
  });

  const Component = props.Component;

  return (
    <ApolloProvider client={client}>
      <Component {...props} authAxios={authAxios} />
    </ApolloProvider>
  );
}

class MyApp extends App {
  render() {
    const { Component, pageProps, shopOrigin } = this.props;
    return (
      <AppProvider i18n={translations}>
        <Provider
          config={{
            apiKey: API_KEY,
            shopOrigin: shopOrigin,
            forceRedirect: true,
          }}
        >
          <RoutePropagator />
          <MyProvider Component={Component} {...pageProps} />
        </Provider>
      </AppProvider>
    );
  }

 

then you can use props.authAxios to access it for any axios call!

Founder / CEO of Folds Page Builder | Available for free at: https://apps.shopify.com/folds
thivatm
Visitor
1 0 0

@noahfromfolds I have implemented the same way you have done to fetch the session token but when I perform a GraphQL query to Billing API it always returns a 401 un authorized error even though I pass the correct access token from the session returned from loadCurrentSession function!

Could you please help me figure out what's wrong in this code?
(I am performing graphql call in '/subscribe' endpoint)

app.prepare().then(async () => {
	const server = new Koa();
	const router = new Router();

	server.keys = [Shopify.Context.API_SECRET_KEY];
	server.use(bodyParser());
	server.use(
		createShopifyAuth({
			accessMode: "offline",
			async afterAuth(ctx) {
				// Access token and shop available in ctx.state.shopify
				const { shop, accessToken, scope } = ctx.state.shopify;
				const host = ctx.query.host;
				ACTIVE_SHOPIFY_SHOPS[shop] = scope;

				const response = await Shopify.Webhooks.Registry.register({
					shop,
					accessToken,
					path: "/webhooks",
					topic: "APP_UNINSTALLED",
					webhookHandler: async (topic, shop, body) =>
						delete ACTIVE_SHOPIFY_SHOPS[shop],
				});

				if (!response.success) {
					console.log(
						`Failed to register APP_UNINSTALLED webhook: ${response.result}`
					);
				}

				ctx.redirect(`/?shop=${shop}&host=${host}`);
			},
		})
	);

	const handleRequest = async (ctx) => {
		await handle(ctx.req, ctx.res);
		ctx.respond = false;
		ctx.res.statusCode = 200;
	};

	router.post("/webhooks", async (ctx) => {
		try {
			await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
			console.log(`Webhook processed, returned status code 200`);
		} catch (error) {
			console.log(`Failed to process webhook: ${error}`);
		}
	});

	router.post(
		"/subscribe",
		async (ctx) => {
			const plan = ctx.request.body.data;
			const currentSession = await Shopify.Utils.loadCurrentSession(
				ctx.req,
				ctx.res,
				false
			);
			console.log(currentSession);

			server.context.client = await createClient(
				currentSession.shop,
				currentSession.accessToken
			);
			// await getAppSubscriptionStatus(ctx);
			await getSubscriptionUrl(ctx, plan); // <-- Billing API call
		}
	);

	router.post(
		"/graphql",
		verifyRequest({ returnHeader: true }),
		async (ctx, next) => {
			await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
		}
	);

	router.get("(/_next/static/.*)", handleRequest); // Static content is clear
	router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
	router.get("(.*)", async (ctx) => {
		const shop = ctx.query.shop;

		// This shop hasn't been seen yet, go through OAuth to create a session
		if (ACTIVE_SHOPIFY_SHOPS[shop] == undefined) {
			ctx.redirect(`/auth?shop=${shop}`);
		} else {
			await handleRequest(ctx);
		}
	});

	server.use(router.allowedMethods());
	server.use(router.routes());
	server.listen(port, () => {
		console.log(`> Ready on http://localhost:${port}`);
	});
});

 

Draccanos
Shopify Partner
1 0 1

I was facing the same problem and only your solution fixed that, thank you for the suggestion!

bashunaimiroy1
Shopify Partner
9 1 5

Very helpful, thanks!

It's worth noting that you don't have to pass your authenticated Axios instance through props- you can actually make a createAuthenticatedAxios.js module, that accepts an `app` argument and returns an authenticated Axios instance in the same way. Then import that wherever in your app you need to make an authenticated Axios call.

juaniwitocorp
Visitor
1 0 0

 

Hi, I used what you described, but how are you making the call from the frontend to the route you created?

In my case, I need to store my app's information in a database outside of shopify, but I'm having the problem that when I carry out the axios to my route, the information I send doesn't travel and I get a 404 error.

 

If you could share with me, how did you solve your axios requests? from the frontend with react you would be helping me a lot. Thank you so much.