Previously (the app server) we got to the point where a Shopify store owner could try to install an application, they would make it to our server, our server would do some work, and then the user would get sent to oblivion. Now we’re going to try and correct that, send the user somewhere slightly more useful.
For the time being, we’re going to completely ignore both the HMAC validation and the nonce. They’re important, but for the sake of satisfaction checking things off they can be deferred (can’t go to production without it though). The next step is to show the application permissions prompt. This is pretty straightforward, we just need to collect a handful of values and then redirect the Shopify application:
- shop: the name of the shop installing the app, looks something like
shop-name.myshopify.com
- API key: the API key of the application (not the API secret key), available in the partner portal – looks something like
c2ecfe6c6b3745cfaaa9a539b1dfe8a7
- scopes: a comma separated list of all of the permissions the app will have. A possible value might be something like:
read_products,read_product_listings,read_orders,read_customers
- redirect URI: where you want the user to go after everything is said and done, e.g. the landing page for your application
- nonce: a random value that can be checked at the next step to ensure the integrity of the process – can be anything, e.g. a UUID works great
- access mode: the lifetime of the the token you’ll ultimately get that will allow you to do the things specified in
scopes
on the user’s behalf. This has to be eitherper-user
orvalue
Collecting all the values takes a bit, but once you’ve done so the next step is very straightforward. You jam them into a a new URL and this is ultimately what we redirect to:
https://{shop}/admin/oauth/authorize?client_id={client_id}&scope={scopes}&redirect_uri={redirect_uri}&state={nonce}&grant_options[]={access_mode}
Implementing this is as straightforward as it seems – amend the previously defined route to redirect somewhere useful:
#[allow(unused_variables)]
#[rocket::get("/install?<hmac>&<shop>&<timestamp>")]
pub fn install(hmac: &str, shop: &str, timestamp: &str) -> Redirect {
let api_key = "801f137828580f71e33ad6b14f75e533"; // Obviously don't use _MY_ key, swap this out for your own
let scopes = "read_products,read_product_listings,read_orders,read_customers"; // These will be custom for you depending on your goals
let nonce = "random-value"; // TODO: This should be unique to each OAuth flow, so hold your nose for a bit...
let redirect = format!("https://{shop}/admin/oauth/authorize?client_id={client_id}&scope={scopes}&redirect_uri={redirect_uri}&state={nonce}&grant_options[]={access_mode}",
shop = shop,
client_id = api_key,
scopes = scopes,
redirect_uri = "this-does-not-exist-yet", // Redirecting to oblivion
nonce = nonce,
access_mode = "value");
Redirect::to(redirect)
}
Now, try the install dance again and see what we get!
Now isn’t that interesting, we redirected somewhere but something else is going on! We’re one step further and have uncovered a brand new way to screw this up. We redirected the user, but in the very beginning we just filled in the field in our app’s configuration with nonsense. Time to go back and fill in a value that lines up with the app. I used “this-does-not-exist-yet” in the code above, so it’s appropriate to be skeptical this will solve all our problems, but lets see.
In the partner portal, click on your app and then “App setup”, “find Allowed redirection URL(s)” and add the value we’re redirecting to (“this-does-not-exist-yet”), then save.
Looks like the value we’ve used is too fake to be useful! Update the redirect_uri
in the code to something that looks more like a URL (e.g. https://this-does-not-exist-yet.test
) and put the corresponding value in the configured “Allowed redirection URL(s)”.
let redirect = format!("https://{shop}/admin/oauth/authorize?client_id={client_id}&scope={scopes}&redirect_uri={redirect_uri}&state={nonce}&grant_options[]={access_mode}",
shop = shop,
client_id = api_key,
scopes = scopes,
redirect_uri = "https://this-does-not-exist-yet.tes
t", // <-- This is what changed here
nonce = nonce,
access_mode = "value");
Now restart your server and do the installation dance again:
We’ve snuck by the validation! We’re so close to having an installable Shopify application, just click that sweet, sweet “Install unlisted app” button and…
I have to be honest, I saw this one coming. Obviously the value we used for redirect_uri
was a bit less useless. We’ll need our application to have at least one more route, so we can redirect users there at this point. Lets fix this quick:
- add a brain-dead API, e.g. I chose (
/callback
) that will return some static text (just so we have some response) - mount the API
- update the
redirect_uri
in your code so to reflect this new value - re-re-update the “Allowed redirection URL(s)” in your Shopify partner portal’s app configuration to be whatever your real value is for the API we just introduce
let redirect = format!("https://{shop}/admin/oauth/authorize?client_id={client_id}&scope={scopes}&redirect_uri={redirect_uri}&state={nonce}&grant_options[]={access_mode}",
shop = shop,
client_id = api_key,
scopes = scopes,
redirect_uri = "https://2f51-204-237-50-186.ngrok.io/callback
", // <-- Updated to be real!
nonce = nonce,
access_mode = "value");
#[rocket::get("/callback")]
pub fn callback() -> &'static str {
"Now what..."
}
fn rocket() -> rocket::Rocket<rocket::Build> {
rocket::build().mount("/", routes![install, callback])
}
For me, the whole URL for this new callback API looks like: https://2f51-204-237-50-186.ngrok.io/callback
. Note that because this is using ngrok, every time you re-establish an ngrok connection you’ll have to update this value and keep it in sync between Shopify configuration and your application code, lest you run into the errors we saw above. This means it’s easiest to keep the ngrok application running the whole time you’re developing so you don’t have to go whitelist the address in your “Allowed Redirection URL(s)” field in your Shopify app configuration constantly.
Shockingly enough, at this point when we try again we will technically have installed your application into the store. To test again you can delete it if you’d like, but we’ll end up the same place if you try and reinstall it in the same store, so dealer’s choice.
After making the code and configuration changes, restart the server, reinstall the application, annnnnddd…..
Looking at the terminal:
We can see a request to:/callback?code=94872a044567b4711e544497b7ce2afc&hmac=14dd56d06cd2962b13cc833dc2472173eeba0223135cdd0b99f5fc45039755ee&host=dG9ieS10ZXN0LXN0b3JlMi5teXNob3BpZnkuY29tL2FkbWlu&shop=toby-test-store2.myshopify.com&state=random-value×tamp=1633314364
That’s a lot of stuff that we’re currently not doing anything with. Revisiting Shopify’s described OAuth flow:
1.
https://shopify.dev/apps/auth/oauth#the-oauth-flowThe merchant makes a request to install the app.
2.The app redirects to Shopify to load the OAuth grant screen and requests the required scopes.
3.Shopify displays a prompt to the merchant to give authorization to the app, and prompts the merchant to log in if required.
4.The merchant consents to the scopes and is redirected to theredirect_uri
.
5. The app makes an access token request to Shopify including theclient_id
,client_secret
, andcode
.
6. Shopify returns the access token and requested scopes.
7. The app uses the access token to make requests to the Shopify API.
8. Shopify returns the requested data.
We’re halfway there! We have an installable Shopify app that is a security nightmare, a GDPR liability, exclusively serves up a single static piece of text, and can’t interact with Shopify at all beyond the first stage of installation, but hey – it’s a Shopify app.
Status
Where we started in this post: We had an application server responding (albeit with garbage) to traffic originating from Shopify
Where we ended with this post: By tackling the first few steps of the Shopify OAuth flow, we have an INSTALLABLE Shopify application that can totally be installed in a store and then it’s in the list of installed apps. Also we have some handy looking data showing up at our callback URL
Next post: We complete the OAuth flow (glossing over a couple major TO-DOs) and obtain the credentials we need to make an authenticated request to Shopify on behalf of the store installing the application