Long shot but ages ago when I had this issue this is what I did.
import { useEffect } from "react";
import {
Card,
Layout,
Page,
Spinner,
Text,
BlockStack,
} from "@shopify/polaris";
const ExitFrame = () => {
useEffect(() => {
if (typeof window !== "undefined") {
const shop = window?.shopify?.config?.shop;
open(`https://${appOrigin}/api/auth?shop=${shop}`, "_top"); //appOrigin is my SHOPIFY_APP_URL
}
}, []);
return (
<>
<Page>
<Layout>
<Layout.Section>
<Card>
<BlockStack gap="200">
<Text variant="headingMd">Security Checkpoint</Text>
<Text variant="bodyMd">Reauthorizing your tokens</Text>
<Spinner />
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
</>
);
};
export default ExitFrame;
Now as far as redirect URL is concerned, this is what my auth looked like:
import {
BotActivityDetected,
CookieNotFound,
InvalidOAuthError,
InvalidSession,
} from "@shopify/shopify-api";
import StoreModel from "../../utils/models/StoreModel.js";
import sessionHandler from "../../utils/sessionHandler.js";
import shopify from "../../utils/shopify.js";
const authMiddleware = (app) => {
app.get("/api/auth", async (req, res) => {
try {
if (!req.query.shop) {
return res.status(500).send("No shop provided");
}
if (req.query.embedded === "1") {
const shop = shopify.utils.sanitizeShop(req.query.shop);
const queryParams = new URLSearchParams({
...req.query,
shop,
redirectUri: `https://${shopify.config.hostName}/api/auth?shop=${shop}`,
}).toString();
return res.redirect(`/exitframe?${queryParams}`);
}
return await shopify.auth.begin({
shop: req.query.shop,
callbackPath: "/api/auth/tokens",
isOnline: false,
rawRequest: req,
rawResponse: res,
});
} catch (e) {
console.error(`---> Error at /api/auth`, e);
const { shop } = req.query;
switch (true) {
case e instanceof CookieNotFound:
case e instanceof InvalidOAuthError:
case e instanceof InvalidSession:
res.redirect(`/api/auth?shop=${shop}`);
break;
case e instanceof BotActivityDetected:
res.status(410).send(e.message);
break;
default:
res.status(500).send(e.message);
break;
}
}
});
app.get("/api/auth/tokens", async (req, res) => {
try {
const callbackResponse = await shopify.auth.callback({
rawRequest: req,
rawResponse: res,
});
const { session } = callbackResponse;
await sessionHandler.storeSession(session);
const webhookRegisterResponse = await shopify.webhooks.register({
session,
});
console.dir(webhookRegisterResponse, { depth: null });
return await shopify.auth.begin({
shop: session.shop,
callbackPath: "/api/auth/callback",
isOnline: true,
rawRequest: req,
rawResponse: res,
});
} catch (e) {
console.error(`---> Error at /api/auth/tokens`, e);
const { shop } = req.query;
switch (true) {
case e instanceof CookieNotFound:
case e instanceof InvalidOAuthError:
case e instanceof InvalidSession:
res.redirect(`/api/auth?shop=${shop}`);
break;
case e instanceof BotActivityDetected:
res.status(410).send(e.message);
break;
default:
res.status(500).send(e.message);
break;
}
}
});
app.get("/api/auth/callback", async (req, res) => {
try {
const callbackResponse = await shopify.auth.callback({
rawRequest: req,
rawResponse: res,
});
const { session } = callbackResponse;
await sessionHandler.storeSession(session);
const host = req.query.host;
const { shop } = session;
await StoreModel.findOneAndUpdate(
{ shop },
{ isActive: true },
{ upsert: true }
); //Update store to true after auth has happened, or it'll cause reinstall issues.
return res.redirect(`/?shop=${shop}`);
} catch (e) {
console.error(`---> Error at /api/auth/callback`, e);
const { shop } = req.query;
switch (true) {
case e instanceof CookieNotFound:
case e instanceof InvalidOAuthError:
case e instanceof InvalidSession:
res.redirect(`/api/auth?shop=${shop}`);
break;
case e instanceof BotActivityDetected:
res.status(410).send(e.message);
break;
default:
res.status(500).send(e.message);
break;
}
}
});
};
export default authMiddleware;
The idea here is if you redirect merchant to yourappurl.com/?shop=storename.myshopify.com
, AppBridge will handle the redirection to your app and without having to worry about a proper redirection.
Though hereโs another thing - @Dylan is correct that you should migrate away from the older OAuth into Managed Installation thatโs much more elegant with this. Just like he said, add this to your shopify.app.toml
file. This means you can remove all of your /api/auth/*
routes and have your auth handled by Shopify.
# false -> use managed installation
use_legacy_install_flow = false
To do auth with managed installation, in your server/index.js
you want to add a new middleware, mine is called isInitialLoad
and looks like this:
import { RequestedTokenType } from "@shopify/shopify-api";
import StoreModel from "../../utils/models/StoreModel.js";
import sessionHandler from "../../utils/sessionHandler.js";
import shopify from "../../utils/shopify.js";
import freshInstall from "../../utils/freshInstall.js";
/**
* @param {import('express').Request} req - Express request object
* @param {import('express').Response} res - Express response object
* @param {import('express').NextFunction} next - Express next middleware function
*/
const isInitialLoad = async (req, res, next) => {
try {
const shop = req.query.shop;
const idToken = req.query.id_token;
if (shop && idToken) {
const { session: offlineSession } = await shopify.auth.tokenExchange({
sessionToken: idToken,
shop,
requestedTokenType: RequestedTokenType.OfflineAccessToken,
});
const { session: onlineSession } = await shopify.auth.tokenExchange({
sessionToken: idToken,
shop,
requestedTokenType: RequestedTokenType.OnlineAccessToken,
});
await sessionHandler.storeSession(offlineSession);
await sessionHandler.storeSession(onlineSession);
const webhookRegistrar = await shopify.webhooks.register({
session: offlineSession,
});
const isFreshInstall = await StoreModel.findOne({
shop: onlineSession.shop,
});
if (!isFreshInstall || isFreshInstall?.isActive === false) {
// !isFreshInstall -> New Install
// isFreshInstall?.isActive === false -> Reinstall
await freshInstall({ shop: onlineSession.shop });
}
console.dir(webhookRegistrar, { depth: null });
}
next();
} catch (e) {
console.error(`---> An error occured in isInitialLoad`, e);
return res.status(403).send({ error: true });
}
};
export default isInitialLoad;
The idea here is with managed installation you want to swap tokens with Shopify to get Online and Offline session tokens and store it in your database.