-
Notifications
You must be signed in to change notification settings - Fork 390
Support for creating an offline token #936
Comments
I am also interested ! Seems like the Thank you |
Hey folks, thanks for your question! When calling Since the feature is there, I'll keep this issue open so we can improve the documentation around it, but please let us know if you have any issues creating offline tokens. |
@paulomarg, I'm wondering how to create an offline session after the latest update (with the Remix template). Before the summer update I used to be able to create an endpoint at my app proxy like so: app.get('/my-endpoint', async(_req, res) => {
const session = await createOfflineSession(_req.query.shop, res);
const client = new shopify.api.clients.Graphql({ session });
const create_result = await client.query({
data: {
query: ...,
variables: ...,
},
});
} Where export async function createOfflineSession(shop, res) {
const session = shopify.api.session.customAppSession(shop);
session.accessToken = await getTokenFromDB(shop);
if (session.accessToken == undefined) {
console.log('no token found for shop ', shop);
res.status(403).send({
success: false,
error: "Unable to create session"
});
return;
}
return session;
} This way I could have an endpoint on my app proxy that would be able to run graphql commands without the need of an admin. But this doesn't work in the new system. How can I create an offline session with the new setup? It seems pretty much all documentation still uses the old Edit |
As javl points out, you can use As for the other question javl asks, right now the Remix package doesn't expose a way to manually create an offline session because we tried to create a simpler interface. We return an For instance, if you're using If there are use cases for having a session that don't require authentication we can see if it makes sense to provide some similar functionality out of the package! |
@paulomarg All of this works separately, and it used to work in my app with the system described above, but because the incoming request is not from a logged in admin I can't have my app respond to the result of the analysis anymore. |
Fair point! I'll take this feedback to the team and we'll look into it :) |
Thanks. I do understand this might not be used a ton, and it could be potentially dangerous if devs don't implement some extra security and validation, but at the same time I feel there are enough use cases that need something like this. With that, and the option to create private products that are only visible from a specific URL / the API (and NOT show up in /collections/all/products.json) Shopify would be perfect ;) |
@paulomarg We're completely stuck developing our app without this function, so I'm wondering if you have any idea if and when this would be implemented, so we can decide if we can continue working with Shopify or need to find a different partner. Is there a place where we can follow along with some sort of roadmap, or progress on these kind of requests? |
@paulomarg i second what @javl is asking. We are creating a new app using the remix template, while there is pleasing signs we have hit a massive block in our development. Trying to create a discount code via the app proxy as we need to speak to our 3rd party loyalty scheme.
Believe we need something ASAP, shall i revert back to 3.47.5 as we need this feature. |
Hey folks, quick update on this: we're going to provide a way to handle unauthenticated (as in not signed by Shopify) requests. We're aligning on what we want the API to be like, but currently we believe it'll look something like this: const {admin} = shopify.unauthenticated.admin(shop); and the We'd welcome your feedback on this API, and if you feel that this would work for your use cases! In the meantime, I realize it's not great, but you can still use import { shopifyApi } from "@shopify/shopify-api";
const config = {
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
apiVersion: LATEST_API_VERSION,
scopes: process.env.SCOPES?.split(","),
restResources,
...(process.env.SHOP_CUSTOM_DOMAIN
? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
: {}),
};
const url = new URL(process.env.SHOPIFY_APP_URL || "");
export const shopifyAPI = shopifyApi({
...config,
hostName: url.hostname,
hostScheme: url.protocol.replace(":", ""),
isEmbeddedApp: true,
});
const shopify = shopifyApp({
...config,
appUrl: process.env.SHOPIFY_APP_URL || "",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
webhooks: {
APP_UNINSTALLED: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
},
hooks: {
afterAuth: async ({ session }) => {
shopify.registerWebhooks({ session });
},
},
}); and in your export const loader = async ({ request }) => {
const { searchParams } = new URL(request.url);
const shop = searchParams.get("shop");
const offlineSessionId = shopifyAPI.session.getOfflineId(shop || "");
const session = await sessionStorage.loadSession(offlineSessionId);
const graphqlClient = new shopifyAPI.clients.Graphql({ session });
// Note the arguments for this client are slightly different
const response = await graphqlClient.query({
data: {
query: {},
variables: {}
}
});
return json({data: response.body.data});
}; |
Thank you for the quick reply and for your workaround to be used in the meantime! I wonder if we'd be able to come up with some reliable ways to verify if a request was allowed to be made. I don't think you really want to try and hide what is going on; you mostly want to be able to verify parameters sent (like the store name) are original and haven't been altered. Some initial thoughts that might be helpful:
|
Just want to confirm this workaround does indeed work. I basically took your example and added Thanks again for the quick response! |
👋 Hey everyone! The team is also discussing providing an authenticate function to authenticate app proxy requests. We think this would be something like
This would be used to authenticate request app proxy requests from online store. @sam-masscreations @javl 2 questions:
Thanks in advance. We want to make this as seamless as possible, and your input can help to that end. |
Now that I have you, can I ask a quick, unrelated question, about where to place a specific request: It would be amazing to have some sort of What would be the best place to post a request to this extend, as there (obviously) isn't a public repo for the Shopify core. Or maybe to have a quick chat about how to implement something like this. |
Hey everyone, We have released version 1.1.0 of @shopify/shopify-app-remix. This includes a new way to create an admin API context without authenticating a request:
We think this satisfies the first use case in this issue and we welcome feedback. As such, I'm going to close this issue. A second use case referenced in this issue is authenticating Storefront App Proxy requests. We are tracking this here. It's the next thing I'm working on. @javl Unfortunately I can't provide that kind of support here. The best thing I can suggest is to contact partner support: https://help.shopify.com/en/support/partners/org-select |
@byrichardpowell @paulomarg Are there any way to query storefront API in the remix route? |
@byrichardpowell |
@huykon Not right now. But I think it's highly likely we'll add one soon. For now you can use @nishikawa-nobuyuki This is just an illustration. The idea is that because it's a Request that Shopify doesn't control, we can't provide an authentication methods for it. So instead, you would implement one yourself. So, |
@byrichardpowell little bit lost here can you show us what |
@marloeffler The I do agree it would be useful if Shopify could provide an example function in the docs, @byrichardpowell. url:
You'll probably want to implement some better security than a fixed string you're send along out in the open though. That's just waiting for something bad to happen. |
@marloeffler The idea is The shop object is just a string, it's the input to
|
@javl the example you provided is helpful, thank you. I'm not sure how best to document this because anything specific we put to illustrate how I'll think about this some more. |
@byrichardpowell On one hand I think having some skeleton function will be useful, but on the other hand I also agree you can't really put something simple in the Shopify repo and NOT expect people to run with this function without adding proper security measurements 🤡 export const authenticateExternalRequest = async (request) => {
// This function needs to return the domain of the shop this request is meant for
// BUT:
// DO NOT USE THIS FUNCTION LIGHTLY
// NOT IMPLEMENTING A SECURE WAY TO VALIDATE THE REQUEST
//____ __ ____ __ __ __
//\ \ / \ / / | | | | | |
// \ \/ \/ / | | | | | |
// \ / | | | | | |
// \ /\ / | | | `----.| `----.
// \__/ \__/ |__| |_______||_______|
//
// LEAD TO UNAUTHORIZED ACCESS AND DAMAGE
} |
@byrichardpowell and @javl thank you so much for the full cover of my question! |
i thin for my usecase this is fair enought to do it like that! Works like a charm, thank you! |
If you do it like that just make sure there is no way for requests to inject their own data or commands, like passing raw graphql strings others could tamper with. Without proper authenticating you have to assume the URL, and anything in the query, is public and can be changed. |
Hi everyone, I'm having real problems handling webhooks in my remix app that are firing off outside of the admin (eg. handling I've seen that
here is my webhooks.jsx file which isn't too changed from the original template (some code taken out for brevity)
but it immediately errors and crashes out at Any thoughts much appreciated |
@jamesdix54 make sure |
Many thanks for your response @joelvh - I'm 99% sure the When you say I need to go through the install flow, could you elaborate a bit more please? There is a route
I assume this is the right area to be in but documentation seems so sparse... It feels like I need to be committing an offline token to a db at some point I just can't for the life of me work out where, or how?! For instance, following some suggestions above I can run
which comes back with
comes back Any help would be much appreciated! |
@jamesdix54 I'm not sure how you originally persisted the session data in your dev environment if you didn't implement the session storage. I'm using shopify-app-js (Remix), which has session storage mechanisms implemented. Maybe take a look at the Express or Remix implementations? |
@joelvh - I've taken the Shopify Remix template as my starter point: https://github.com/Shopify/shopify-app-template-remix I haven't done anything around session persistence as I couldn't find documentation to suggest I needed to. It seems to "just work" out of the box when I'm testing in my dev environment, but not in prod. The authenticate.admin(request) seems to run correctly for a few minutes after installing it, but if I try a transaction 30 mins after installing, it fails. If it's helpful, on install in the logs I'm seeing:
So it seems to be creating the correct token, and like I say, working in development environment, but not in production. Any pointers as to what may be happening? |
@jamesdix54 my guess is it's using cookie storage and you're losing the cookie. Check what cookies are set. But for production, you'll want to use a database of some sort to store the offline token. Each of the storage options in the link I provided should have a README.md with some instructions. However, agreed that docs are sparse -- my PR is still open to update the DynamoDB docs. |
This link is showing a File not found error |
Overview
For background processes, it's pretty unclear in the documentation how I would go about making the equivalency of an "offline session" in the terminology of the package.
I would expect I would be able to pass an access token, shop url etc to my "rest" instance and be able to use the functions of the package without having to deal with an actual user "session".
I could 100% have missed this feature in the package - if so, maybe the reference to that could be made more present in the documentation?
The text was updated successfully, but these errors were encountered: