Dedicated to the Hydrogen framework, headless commerce, and building custom storefronts using the Storefront API.
Hello.
I'm a new to Shopify app development and trying to get both offline and online token by making a custom app which is made by the cli command "shopify app create node".
In this app, since I would like to use both Admin and Storefront API, I want to get both offline and online token during the auth process. However, I can't really understand how could I do that as I can't find any reference for that process.
Appreciate if anybody has solution for this.
Thanks!
Solved! Go to the solution
This is an accepted solution.
Hi @buildpath, that's the way we implemented it in NodeJS + MongoDB:
app.get("/auth/callback", async (req, res) => {
try {
const session = await Shopify.Auth.validateAuthCallback(
req,
res,
req.query
);
// save online token (session.accessToken)
Website.countDocuments({ shop: session.shop }, function (err, count) {
if (count == 0) {
var website = {
id: session.id,
shop: session.shop,
access_token: session.accessToken,
creation: Date.now(),
modified: Date.now(),
};
Website.create(website, function (err, doc) {
if (err) {
console.log("Something wrong when creating data!");
console.log(err);
}
});
}
});
// register uninstall webhook
const response = await Shopify.Webhooks.Registry.register({
shop: session.shop,
accessToken: session.accessToken,
topic: "APP_UNINSTALLED",
path: "/webhooks",
});
if (!response["APP_UNINSTALLED"].success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to second callback
const redirectUrl = await Shopify.Auth.beginAuth(
req,
res,
req.query.shop,
"/auth/callback2",
false
);
res.redirect(redirectUrl);
} catch (e) {
switch (true) {
case e instanceof Shopify.Errors.InvalidOAuthError:
res.status(400);
res.send(e.message);
break;
case e instanceof Shopify.Errors.CookieNotFound:
case e instanceof Shopify.Errors.SessionNotFound:
// This is likely because the OAuth session cookie expired before the merchant approved the request
res.redirect(`/auth?shop=${req.query.shop}`);
break;
default:
res.status(500);
res.send(e.message);
break;
}
}
});
app.get("/auth/callback2", async (req, res) => {
try {
const session = await Shopify.Auth.validateAuthCallback(
req,
res,
req.query
);
const host = req.query.host;
// save offline token (session.accessToken)
await Website.findOneAndUpdate(
{ shop: session.shop },
{ offline_token: session.accessToken }
);
// Redirect to app with shop parameter upon auth
res.redirect(`/?shop=${session.shop}&host=${host}`);
} catch (e) {
switch (true) {
case e instanceof Shopify.Errors.InvalidOAuthError:
res.status(400);
res.send(e.message);
break;
case e instanceof Shopify.Errors.CookieNotFound:
case e instanceof Shopify.Errors.SessionNotFound:
// This is likely because the OAuth session cookie expired before the merchant approved the request
res.redirect(`/auth?shop=${req.query.shop}`);
break;
default:
res.status(500);
res.send(e.message);
break;
}
}
});
}
Hope it helps. Good luck.
Hello Nmats, did you find a solution to get offline token? I'm trying to do the same thing but this feature is not well documented.
Thanks a lot!
Hi Visidea, unfortunately not yet.
FYI: we developed a solution in 2 steps, the first saves the online token, then redirects to the second endpoint. In this second step, we create the offline token and save it.
@visidea can you share any of that code here? I've been searching for a couple days to find some documentation and discussion of this.
This is an accepted solution.
Hi @buildpath, that's the way we implemented it in NodeJS + MongoDB:
app.get("/auth/callback", async (req, res) => {
try {
const session = await Shopify.Auth.validateAuthCallback(
req,
res,
req.query
);
// save online token (session.accessToken)
Website.countDocuments({ shop: session.shop }, function (err, count) {
if (count == 0) {
var website = {
id: session.id,
shop: session.shop,
access_token: session.accessToken,
creation: Date.now(),
modified: Date.now(),
};
Website.create(website, function (err, doc) {
if (err) {
console.log("Something wrong when creating data!");
console.log(err);
}
});
}
});
// register uninstall webhook
const response = await Shopify.Webhooks.Registry.register({
shop: session.shop,
accessToken: session.accessToken,
topic: "APP_UNINSTALLED",
path: "/webhooks",
});
if (!response["APP_UNINSTALLED"].success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to second callback
const redirectUrl = await Shopify.Auth.beginAuth(
req,
res,
req.query.shop,
"/auth/callback2",
false
);
res.redirect(redirectUrl);
} catch (e) {
switch (true) {
case e instanceof Shopify.Errors.InvalidOAuthError:
res.status(400);
res.send(e.message);
break;
case e instanceof Shopify.Errors.CookieNotFound:
case e instanceof Shopify.Errors.SessionNotFound:
// This is likely because the OAuth session cookie expired before the merchant approved the request
res.redirect(`/auth?shop=${req.query.shop}`);
break;
default:
res.status(500);
res.send(e.message);
break;
}
}
});
app.get("/auth/callback2", async (req, res) => {
try {
const session = await Shopify.Auth.validateAuthCallback(
req,
res,
req.query
);
const host = req.query.host;
// save offline token (session.accessToken)
await Website.findOneAndUpdate(
{ shop: session.shop },
{ offline_token: session.accessToken }
);
// Redirect to app with shop parameter upon auth
res.redirect(`/?shop=${session.shop}&host=${host}`);
} catch (e) {
switch (true) {
case e instanceof Shopify.Errors.InvalidOAuthError:
res.status(400);
res.send(e.message);
break;
case e instanceof Shopify.Errors.CookieNotFound:
case e instanceof Shopify.Errors.SessionNotFound:
// This is likely because the OAuth session cookie expired before the merchant approved the request
res.redirect(`/auth?shop=${req.query.shop}`);
break;
default:
res.status(500);
res.send(e.message);
break;
}
}
});
}
Hope it helps. Good luck.
@visidea this is EXACTLY what I needed - thank you! I need to add an additional auth callback for the offline token, I see. Perfect.
UPDATE: I got it all working perfectly. My app is now getting both online and offline access tokens. Thanks again!
Thanks so much, this helped me setup my tokens too! I just have one question about the storing process of the offline token in your auth/callback2 route. You run a code snippet that stores the token as follows:
// save offline token (session.accessToken)
await Website.findOneAndUpdate(
{ shop: session.shop },
{ offline_token: session.accessToken }
);
But there's a code snippet just before that which already stores the session and token for you if I'm not mistaken:
const session = await Shopify.Auth.validateAuthCallback(
req,
res,
req.query
);
The function Shopify.Auth.validateAuthCallback() stores the session/token for you using your session storage handler, here's a code snippet of what the function does:
const sessionStored = await Context.SESSION_STORAGE.storeSession(session);
if (!sessionStored) {
throw new ShopifyErrors.SessionStorageError(
'Session could not be saved. Please check your session storage functionality.',
);
}
The entire function implementation of validateAuthCallback
So what I'm asking is why is there a need to manually store the tokens in your auth functions? I'm very new to shopify so I could have overlooked something.
Hi @SilasGrygier,
I need to save the tokens for later access, for example, to access the Shopify APIs. We use MongoDB as data storage, so that's the way we implemented persistence.
If you have other business requirements and you don't need to retrieve the tokens later, you can skip that phase.
What I'm trying to say is that you wrote 2 code snippets that are doing the same thing:
// save offline token (session.accessToken)
await Website.findOneAndUpdate(
{ shop: session.shop },
{ offline_token: session.accessToken }
);
Is doing the same thing as this:
const session = await Shopify.Auth.validateAuthCallback(
req,
res,
req.query
);
So why include both because validateAuthCallback already stores the tokens in your database