Let me try and explain it better:
My app is an embeded one. Its client side communicates with my server only through ajax. An user of the app does not have a session for my service in the form of a cookie or anything. They have an API bearer token, which the client side of my app uses to communicate with the API of my app (the backend).
More than one storeowner of one single shop can register in my app backend. In all cases a storeowner is mapped to a Shopify store record in my backend. The Shopify store record in the backend holds the shop name and the Shopify access token for the store, which my backend uses to communicate with the Shopify API and thus the store.
So hypothetically if the user wants to go to my app dashboard, which will present some analysis of the data in their store, they need to login, get the bearer token to communicate with my app API and then the dashboard client side of my app will call something like getStoreInfo in my app API. The backend code for the getStoreInfo will use the Shopify access token saved for the store of the user and call Shopify to get the store information and do tha analysis.
So here is what happens when an user uses my app.
1. When a user clicks on my app, or installs it for that matter, my app callback url is requested. In the callback I get four parameters: shop, hmac, timestamp, signature.
2. I validate the request, that it comes from Shopify and from the shop that is in the shop parameter. This is all this thread is about. Validating the request with just the shop, hmac, timestamp parameters and without the access code one.
3. Next I check my database if there is already a record for this store and if there is I check whether the Shopify access token in the record is a valid one (do a test request to get the shop name).
4. If the Shopify access token is valid I redirect the user to my app dashboard. An attempt to go to my app dashboard will result in checking if the user has a valid session for my own service (a bearer token to communicate with my app API). If not I redirect them to the login/register page.
5. If the Shopify access token is not a valid one or there is no record for the shop in my database, I redirect the user to my app authorization url, and do the OAuth dance. When I receive the access token I save it in the database, create a record for the current store and redirect the user to the login/register page.
In both cases the user will most probably end up at the login/register screen. And this is where the security problem begins.
6. Suppose that the user decides to register in my app. I will have to map their account to the store record in my database so I know, which store this user is working with. When they click the register button they call my API regsiter endpoint and the shop parameter is passed to this endpoint so that I can do the mapping. What could happen:
- The store owner of Store1 has already registered in my app. And there is a record for Store1 in my database with a shop name and a valid Shopify access token.
- The store owner of Store2 is also using my app and going through the registration process and tweaks the request to my API register endpoint to have Store1 as the shop parameter and not Store2. My service backend code will create an account in my database for the store owner and map their account to the record for Store1. Whatever function my service performs for this store owner will be against Store1 and not Store2.
The way to avoid this is to validate that the shop parameter which comes to my register/login endpoint is the actual store from which the store owner is currently using my app.
I did not have a problem to validte this in Step 5 because I could validate the authorization callback, because it is passed: hmac, shop, code, signature, timestamp. I have the code parameter as compared to Step 4 where I do not have it, and the documentation clearly describes how to validate the request when you have the code parameter.
But if the user does not get to Step 5 but instead is at Step 4, where I have determined that the current store already has a record in my database and a valid Shopify access token and the user just needs to register or login. Then they can tweak the register/login call to my service API and send whatever they want for the store parameter. And we end up with the security problem.
I hope this makes it clear.
Again, the way I see it:
Validating a request, which comes with the hmac, shop, signature and timestamp parameters is not only to validate that it comes from Shopify but that it comes from the shop specified in the shop parameter. Because this shop parameter is going to be used by the app. And if this parameter is tweaked, then you can end up in the situation I have described.