Shopify app with Rust: Part 4 – Get OAuth Authorization Code

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 either per-user or value

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!

“The redirect_uri is not whitelisted”

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.

Fails validation

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.test", // <-- This is what changed here
		nonce = nonce,
		access_mode = "value");

Now restart your server and do the installation dance again:

Made it to the permissions page

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…

We’ve redirected to oblivion again

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:

  1. add a brain-dead API, e.g. I chose (/callback) that will return some static text (just so we have some response)
  2. mount the API
  3. update the redirect_uri in your code so to reflect this new value
  4. 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…..

Successful redirection!

Looking at the terminal:

terminal output

We can see a request to:
/callback?code=94872a044567b4711e544497b7ce2afc&hmac=14dd56d06cd2962b13cc833dc2472173eeba0223135cdd0b99f5fc45039755ee&host=dG9ieS10ZXN0LXN0b3JlMi5teXNob3BpZnkuY29tL2FkbWlu&shop=toby-test-store2.myshopify.com&state=random-value&timestamp=1633314364

That’s a lot of stuff that we’re currently not doing anything with. Revisiting Shopify’s described OAuth flow:

1. The 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 the redirect_uri.
5. The app makes an access token request to Shopify including the client_id, client_secret, and code.
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.

https://shopify.dev/apps/auth/oauth#the-oauth-flow

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s