App reviews, troubleshooting, and recommendations
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!
Solved! Go to the solution
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!
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!
@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}`);
});
});
I was facing the same problem and only your solution fixed that, thank you for the suggestion!
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.
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.
Shopify and our financial partners regularly review and update verification requiremen...
By Jacqui Mar 14, 2025Unlock the potential of marketing on your business growth with Shopify Academy's late...
By Shopify Mar 12, 2025Learn how to increase conversion rates in every stage of the customer journey by enroll...
By Shopify Mar 5, 2025