Open adrianocr opened 4 years ago
Flask-Dance wouldn't include an X-Shopify-Access-Token
header no, it has not way of knowing you have to do so. This is a bit unfortunate on Shopify's side, the accepted header for this is Authorization
, usually of the from Authorization: Bearer <token>
which is what it'll do.
It looks like in order to fix this you'll have to submit a compliance fix to https://github.com/requests/requests-oauthlib/tree/master/requests_oauthlib/compliance_fixes to deal with the header.
I'm not quite sure what's up with the other error, but since it errors out on InvalidClientIdError
it seems to be that you didn't configure Flask Dance with the client ID from Shopify. Are you sure you copied the API Key into client_id and API Secret into client_secret?
@daenney yes, I just verified and then also hardcoded the api key and secret directly into shopify.py instead of pulling it from the config file. Still doesn't work.
I'm guessing the issue is that the proper header isn't in place like Shopify expects so it doesn't find the key and therefore: invalid/missing.
Is there a way to hack / monkey pactch in the key into the header at least for testing purposes? Like a way to tap into the request call and manually include:
headers = {"X-Shopify-Access-Token": access_token}
shopify.get('/products.json', headers=headers)
Or something like that?
Additionally is there a way for me to check if flask-dance has actually successfully negotiated the access_token and access it? Such as shopify_bp.session.access_token
or something? If yes I can pull that token and try to manually insert it into the header some way or another.
Edit: additionally I'm not sure if it'd be any help but Shopify has an official Koa middleware for handling oauth that might be helpful in understanding how they handle the ouath process? https://github.com/Shopify/quilt/tree/master/packages/koa-shopify-auth
Assuming it's stored it and didn't wipe it due to the next error, blueprint.token["access_token"]
lets you get it, or current_app.blueprints["shopify"].token["access_token"]
Nope, that's returning NoneType so I assume it hasn't actually been set/stored.
I guess this if failing earlier in the process than I thought. Any tips on what I should try? I'm genuinely at a loss for where to even start trying to correct this.
It appears that you're using the shopify_bp.from_config
feature incorrectly. As the documentation states, it's used for dynamically loading values from the Flask application config (app.config
) into your blueprint. This can be useful if you want to set the client ID and client secret using environment variables, for example.
In your code, you have this:
shopify_bp.from_config["client_id"] = "SHOPIFY_OAUTH_CLIENT_ID"
shopify_bp.from_config["client_secret"] = "SHOPIFY_OAUTH_CLIENT_SECRET"
shopify_bp.from_config["scope"] = "write_products,read_products,read_script_tags,write_script_tags"
shopify_bp.from_config["grant_options"] = "per-user"
The first line says "Look for app.config["SHOPIFY_OAUTH_CLIENT_ID"]
and use that as the client_id
if it's set", which is reasonable. The third line says "Look for app.config["write_products,read_products,read_script_tags,write_script_tags"]
, and use that as the scope
if it's set", which is probably not what you want.
In your code, you're using app.config.from_pyfile('config.cfg')
to populate app.config
. That's perfectly fine, but I don't know what's in that config.cfg
file. Are you setting these values correctly?
@singingwolfboy sorry, my intention there was to just imply (so to speak) that the config.cfg file was fine and not the source of the problem but I should have probably included that. So here is config.cfg
:
TESTING = True
DEBUG = True
TEMPLATES_AUTO_RELOAD = True
SHOP_NAME = 'myshopifystorename'
SHOPIFY_OAUTH_CLIENT_ID = '49cbab21a048fea4f7a8ca9d38891e61'
SHOPIFY_OAUTH_CLIENT_SECRET = 'f18e1ed6801f3203053908815c4bf656'
SCOPE = 'write_products,read_products,read_script_tags,write_script_tags'
And you're completely right about the scope entry, but that was just for testing purposes. Initially I had it as shopify_bp.from_config["scope"] = "SCOPE"
like you'd expect.
Did you end up fixing this @adrianocr
Full working code would be appreciated.
Please bear with me on this long-winded essay I'm about to write because I'm not sure I know how to create this issue without it...
So Shopify uses the Authorization Code Grant flow. Their entire oauth documentation is located here: https://help.shopify.com/en/api/getting-started/authentication/oauth. Basically:
So I created a basic app.py with the following contents based on https://github.com/singingwolfboy/flask-dance-github/blob/master/github.py:
Then I duplicated flask_dance/contrib/github.py into
myproject_base_dir/contrib/shopify.py
:In Shopify the app is set up like this:
When I fire this up and go to to https://apps.MYDOMAIN.com shopify redirects to:
https://{store_name}.myshopify.com/admin/oauth/request_grant?client_id=49cbab21a048fea4f7a8ca9d38891e61&redirect_uri=https%3A%2F%2Fapps.MYDOMAIN.com%2Flogin%2Fshopify%2Fauthorized&scope=write_products%2Cread_products%2Cread_script_tags%2Cwrite_script_tags&state=fNUFy4IHD7rzRASaijGpfJymq8OFqq
And if I accept the installation at the url above, I'm then redirected to:
https://apps.MYDOMAIN.com/login/shopify/authorized?code=b3c2e7465335efd3c71339066e6fbca5&hmac=837dead7f446d07bc1f6c234c77c2e23c3d6fdb945562181c823513c9cf66468&shop={shop_name}.myshopify.com&state=fNUFy4IHD7rzRASaijGpfJymq8OFqq×tamp=1579452037
and that page displays (via flask's debug screen):
oauthlib.oauth2.rfc6749.errors.InvalidClientIdError: (invalid_request) Could not find Shopify API application with api_key
Here is the full traceback from flask debug:
It seems to me the issue is that flask-dance isn't successfully negotiating the access_token exchange which shopify which is described at https://help.shopify.com/en/api/getting-started/authentication/oauth#step-3-confirm-installation. Additionally to make authenticated requests the request needs to include a
X-Shopify-Access-Token: {access_token}
header which I'm not sure flask-dance does, correct?I've been banging my head against this for 3-4 days now. I've read the full flask-dance documentation (top to bottom) and the requests documentation (top to bottom) and I can't figure it out. I'm afraid I'm just not experienced enough to understand what's happening 😞
_P.s. I understand the above URL examples I included have clientids and secrets in them but it doesn't matter, I've already revoked the api credentials in the app.