Shopify / shopify-api-js

Shopify Admin API Library for Node. Accelerate development with support for authentication, graphql proxy, webhooks
MIT License
944 stars 390 forks source link

Make authenticated request from proxy: Missing Authorization header #632

Closed valorloff closed 1 year ago

valorloff commented 1 year ago

Issue summary

Hi! Thanks for the abolition customSessionStorage and switching to shop name identification! But after migrate to useAppQuery and api-js-V.6, it is not clear where to get the sessionId for making requests, coming from the App proxy.

app.get('/api/proxy_shopify', async (req, res) => {
    await redirectToAuth(req, res, app);

    const sessionId = await shopify.session.getCurrentId({
      rawRequest: req,
      rawResponse: res,
      isOnline: true,
    });
.............................

i get [shopify-api/ERROR] Missing Authorization header, was the request made with authenticatedFetch? | {isOnline: false}

Expected behavior

I was sure that the incoming request headers should contain the necessary session object for requests

Steps to reproduce the problem

When requests are received to app proxy url, request data don't contain valid authenticated data, that is contained when a request is received from internal useAppQuery (with authanticatedFetch) request

Question

How can valid session data from app proxy be obtained?

valorloff commented 1 year ago

Hi! I got offline sessionId by shopify.session.getOfflineId(req.query.shop); and now I can make requests is it correct way?

valorloff commented 1 year ago

Unfortunately In production shopify.session.getOfflineId(req.query.shop); don't works. Please, what is correct way to make auth requests during incoming requests from App proxy ?

timmatheson commented 1 year ago

I am also getting this exact same behavior with Shopify Flow triggers. Even after scaffolding a stock extension from the shopify-cli generators. All inbound requests loose the shop parameter and go to /auth please help!

valorloff commented 1 year ago

in shopify apps docs specified proxy URL:

GET /apps/awesome_reviews/extra/path/components?extra=1&extra=2 HTTP/1.1
  Host: shop-domain.com
  Cookie: csrftoken=01234456789abcdef0123456789abcde;
  _session_id=1234456789abcdef0123456789abcdef;
  _secure_session_id=234456789abcdef0123456789abcdef0

i see there _session_id, possible this is desired sessionId, needed for sqliteSessionStorage.loadSession(sessionId); ?

valorloff commented 1 year ago

if you try

shopify.auth.callback({
        rawRequest: req,
        rawResponse: res,
      });

i get CookieNotFound [Error]: Cannot complete OAuth process. Could not find an OAuth cookie for shop

due they are disabled in app proxy request! So, please, how to make authenticated request from app proxy?

brdsmth commented 1 year ago

I am experiencing the same issue. When I hardcode the sessionId inside validate-authenticated-session.js in the shopify.validateAuthenticatedSession() middleware I get the correct expected behavior.

I created my app one day ago with the most updated node app in the shopify-cli and am following the documentation. I am making a request to my app via postman to a custom api route behind the app.use('/api/*, shopify.validateAuthenticatedSession()) middleware. I installed my app on a test store and successfully created a session via MongoDBSessionStorage and saved the session to the database.

Inside validateAuthenticatedSession() there's a try / catch block calling api.session.getCurrentId() with the req, res, isOnline as input parameters. This was not working for me so I experimented with hardcoding my test store's sessionId to troubleshoot and afterwards it worked properly. I'm not sure if this is an issue with getCurrentId() or the MongoDBSessionStorage library.

The below try / catch block works as intended (hardcoded) but does not work out-of-the-box when making request from postman with this url:

GET {{ shopify_provided_ngrok_url }}/api/{{ custom_route }}?shop=my-shop.myshopify.com

try {
        sessionId = await api.session.getCurrentId({
          isOnline: config.useOnlineTokens,
          rawRequest: req,
          rawResponse: res
        });
        /** 
        * Below line hardcoded in to troubleshoot the issue
        */
        sessionId = 'offline_my-shop.myshopify.com'
      } catch (error) {
        await config.logger.error(`Error when loading session from storage: ${error}`);
        await handleSessionError(req, res, error);
        return undefined;
      }

The above edit got my app working as intended and I'm able to continue local development via postman, but it is not a fix. My session storage config is set up like the below. Do I need to define a handler for getting the current id?

const shopify = shopifyApp({
  api: {
    apiVersion: LATEST_API_VERSION,
    restResources,
    billing: undefined, // or replace with billingConfig above to enable example billing
  },
 ...
  sessionStorage: new MongoDBSessionStorage(
    'mongodb_connection_string',
    'collection_name',
  ),
});
valorloff commented 1 year ago

from appProxy request you are getting shop domain name, then use it in const sessions = await mongoSessionStorage.findSessionsByShop(req.query.shop); for getting session object

did I understand you correctly?

brdsmth commented 1 year ago

It looks like the most recent version of the node app included in the shopify-cli already has some middleware for parsing out the shop parameter, so I wasn't using that line you mentioned (though maybe I should be?).

Out of the box the node app template has this line

app.use("/api/*", shopify.validateAuthenticatedSession());

And I just set up a router for */api/health to see if my external requests were getting authenticated correctly with that middleware function, and they were not. So I dug into the shopify.validateAuthenticatedSession() library function in the @shopify/shopify-app-express package. It looks like the line

sessionId = await api.session.getCurrentId({
          isOnline: config.useOnlineTokens,
          rawRequest: req,
          rawResponse: res
        });

is not reading my shop parameter from the req correctly (sessionId was returning null), so I hardcoded it and things worked. I could have something wrong as I'm somewhat new to developing shopify apps.

valorloff commented 1 year ago

This topic about "authenticated request from proxy", and i come to a conclusion, that in request object, coming from proxy, there is no sessionId, but there is a shop name, by which we get session object

brdsmth commented 1 year ago

Understood, apologies for the spam.

dongido001 commented 1 year ago

Hi @valorloff,

Could you let me know if you managed to get this fixed?

I am facing similar issue. But I think for a request to be passed through app proxy, it's endpoint has to start with "/apps/*"

but you are using /api/*, did it work?

valorloff commented 1 year ago

@dongido001, the entry point can be any, do not confuse with the app proxy settings. And yes, this fixed by mongoSessionStorage.findSessionsByShop, as i have already explained above

dongido001 commented 1 year ago

here is my setting:

Screenshot 2023-01-15 at 11 26 58

This is the endpoint the app proxy is targeted to, which is working:

Screenshot 2023-01-15 at 11 28 43

But when I try to access the proxy, it does not work:

Screenshot 2023-01-15 at 11 33 19

I am so confused as to what I am doing wrong

valorloff commented 1 year ago

you messed up everything, you need to specify the correct url in the fetch: fetch(/apps/proxy/api/proxy_shopify?......`

dongido001 commented 1 year ago

thanks you @valorloff

Paula-Alohas commented 1 year ago

Hey @brdsmth I am having same issue you had:

I am having problems with finding the way to avoid this /* so I can return my own response. Maybe it's a very basic concept but can't find the solution. How did you solve your proxy authetication in the end? Thank you for your help!

valorloff commented 1 year ago

@brdsmth you need use the /api endpoint, the request come to, which is contain shop name, that is being used for session_id, that is being used for getting session from storage

Paula-Alohas commented 1 year ago

Hi @valorloff Thank you for your help. If I do that then I receive a response saying I am missing the Authorization header. Am I missing something else? the validateAuthentication already is implemented with all the process inside the middleware. I cannot choose to get the Id by findSessionsByShop....Obviusly I might be missing a step

dongido001 commented 1 year ago

@Paula-Alohas. are you using the latest CLI template (3.0)?

Then you need to move the proxy endpoints to the top. make sure it above "app.get(shopify.config.auth.path, shopify.auth.begin());". that's if I understand you correctly

brdsmth commented 1 year ago

@Paula-Alohas I fixed this by implementing a middleware function to check for session details in mongodb

in web/index.js I replaced

// All endpoints after this point will require an active session
// app.use("/api/*", shopify.validateAuthenticatedSession());
app.use('/api/*', checkForSession)

with my middleware function being below

export const checkForSession = async function (req, res, next) {
    const session = await db.collection('shopify_sessions').findOne({  /** whatever index **/ })
    res.locals.shopify = { session: {} }
    res.locals.shopify.session = session
    next()
}

You may need to experiment with what you index the db with - for instance you could pass shop name as a query param and get it from req or pass something else that would help you find session in db. If you're making request from inside embedded app you may need to parse the referer or the shop name straight from the url and use that to index db. But this middleware strategy is working fine for me now

Paula-Alohas commented 1 year ago

Thank you so much! I will definitely try this solution. Sounds like the way to go for me

valorloff commented 1 year ago

As far as I understand, request from app proxy don't contain Authorization header at all, only shop name. Authorization header you can receive only from app request, not from online store.

ionescurares commented 1 year ago

@brdsmth

Tried your middleware solution and it partially fixed the issue for me :(. I am getting [shopify-app/ERROR] ensureInstalledOnShop did not receive a shop query argument | {shop: undefined} . Any thoughts on this would be appreciated

github-actions[bot] commented 1 year ago

This issue is stale because it has been open for 90 days with no activity. It will be closed if no further action occurs in 14 days.

github-actions[bot] commented 1 year ago

We are closing this issue because it has been inactive for a few months. This probably means that it is not reproducible or it has been fixed in a newer version. If it’s an enhancement and hasn’t been taken on since it was submitted, then it seems other issues have taken priority.

If you still encounter this issue with the latest stable version, please reopen using the issue template. You can also contribute directly by submitting a pull request– see the CONTRIBUTING.md file for guidelines

Thank you!