When I try and install my deployed Shopify App, I get redirected to a page that says "There’s no page at this address."
It's strange because when I go to install the app, the first link that is generated is:
https://shopify-app.herokuapp.com/?hmac=d61597ca3ea6ca74b8bd6ea8f8bcc812b4382fad6f15434c0158bc4c3ade519a&host=dXBsaWZ0ZWQtY29tbWVyY2UtZGV2Lm15c2hvcGlmeS5jb20vYWRtaW4&shop=dev.myshopify.com×tamp=1657326911
Which then redirects to the error page.
When I instead manually use this link:
https://shopify-app.herokuapp.com/api/auth?shop=dev.myshopify.com
The Oauth process works without an issue.
In my DEV environment, the "install" link takes me to: https://test.ngrok.io/api/auth?shop=dev.myshopify.com unless there is an entry in the database for ACTIVE_SHOPIFY_SHOPS.
When there is an entry in the database, then the install link is the same format as when I'm having issues with PROD:
https://test.ngrok.io/hmac=2c3d2200bee25b5ea3736ebddd06494b1ee9a5f2f5ffa310d806417c469f9f10&host=dXBsaWZ0ZWQtY29tbWVyY2UtZGV2Lm15c2hvcGlmeS5jb20vYWRtaW4&shop=dev.myshopify.com×tamp=1657327896
And I'm also redirected to the "There’s no page at this address" page.
However, my production database is empty. This only started happening on the deployed Heroku PROD version of my app.
I've been at this for almost 8 hours now and can't for the life of me figure out why it's not working.
I'm using the Shopify CLI to scaffold the project.
Any idea what's going on?
This is the catchall route that checks for a value and redirects to auth:
app.use("/*", async (req, res, next) => {
const shop = req.query.shop;
const shopValue = await pool.query(`SELECT * FROM shop WHERE shop_url=$1`, [
shop,
]);
if (shopValue?.rows[0]?.scope) {
ACTIVE_SHOPIFY_SHOPS[shop] = shopValue.rows[0].scope;
} else {
ACTIVE_SHOPIFY_SHOPS[shop] = undefined;
}
// Detect whether we need to reinstall the app, any request from Shopify will
// include a shop in the query parameters.
if (app.get("active-shopify-shops")[shop] === undefined && shop) {
res.redirect(`/api/auth?shop=${shop}`);
} else {
const fs = await import("fs");
const fallbackFile = join(
isProd ? PROD_INDEX_PATH : DEV_INDEX_PATH,
"index.html"
);
res
.status(200)
.set("Content-Type", "text/html")
.send(fs.readFileSync(fallbackFile));
}
});
These are my auth routes:
export default function applyAuthMiddleware(
app,
{ billing = { required: false } } = { billing: { required: false } }
) {
app.get("/api/auth", async (req, res) => {
console.log("API AUTH");
if (!req.query.shop) {
res.status(500);
return res.send("No shop provided");
}
if (!req.signedCookies[app.get("top-level-oauth-cookie")]) {
return res.redirect(`/api/auth/toplevel?shop=${req.query.shop}`);
}
const redirectUrl = await Shopify.Auth.beginAuth(
req,
res,
req.query.shop,
"/api/auth/callback/offline",
false
);
res.redirect(redirectUrl);
});
app.get("/api/auth/toplevel", (req, res) => {
console.log("AUTH TOPLEVEL");
res.cookie(app.get("top-level-oauth-cookie"), "1", {
signed: true,
httpOnly: true,
sameSite: "strict",
});
res.set("Content-Type", "text/html");
res.send(
topLevelAuthRedirect({
apiKey: Shopify.Context.API_KEY,
hostName: Shopify.Context.HOST_NAME,
shop: req.query.shop,
})
);
});
app.get("/api/auth/callback/offline", async (req, res) => {
console.log("OFFLINE CALLBACK");
try {
const session = await Shopify.Auth.validateAuthCallback(
req,
res,
req.query
);
//Save the offline access token
await pool.query(
`UPDATE shop SET offline_access_token = $1 WHERE shop_url = $2`,
[session.accessToken, session.shop]
);
//get the online token
const redirectUrl = await Shopify.Auth.beginAuth(
req,
res,
req.query.shop,
"/api/auth/callback",
true
);
// Redirect to app with shop parameter upon auth
res.redirect(redirectUrl);
}
});
app.get("/api/auth/callback", async (req, res) => {
console.log("AUTH CALLBACK");
try {
const session = await Shopify.Auth.validateAuthCallback(
req,
res,
req.query
);
const host = req.query.host;
app.set(
"active-shopify-shops",
Object.assign(app.get("active-shopify-shops"), {
[session.shop]: session.scope,
})
);
const responses = await Shopify.Webhooks.Registry.registerAll({
shop: session.shop,
accessToken: session.accessToken,
});
Object.entries(responses).map(([topic, response]) => {
if (!response.success && !gdprTopics.includes(topic)) {
console.log(
`Failed to register ${topic} webhook: ${response.result.errors[0].message}`
);
}
});
// Redirect to app with shop parameter upon auth
res.redirect(redirectUrl);
}
});
}