Access a community of over 900,000 Shopify Merchants and Partners and engage in meaningful conversations with your peers.
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!
@SaphiraDev 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!
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.