Shopify_app: [Docs request] Add concurrent access mode examples (offline & online auth)

Created on 15 Aug 2018  路  6Comments  路  Source: Shopify/shopify_app

Overview

Shopify's OAuth API and Omniauth gem supports both offline and online access modes. We should provide a clear example of how to use them both, concurrently, in our apps. More specifically, how to setup online access for only the ShopifyApp::AuthenticatedController while still maintaining offline access for ShopifyApp::AppProxyVerification and ShopifyApp::WebhookVerification.

Previous discussions

This has been discussed before, see #580, but there has not been a clear solution moving forward. As of today, there aren't any public repos in Github implementing this either. From what I understood, you can build two Shopify tokens, one for offline and one for online, but I'm not sure how to scope them properly.

Examples

Here's an example of what I'm talking about:

Offline setup (default) <-- This works

AuthenticatedControllers, proxy app, and webhooks work properly, but you can't access individual user info on AuthenticatedControllers.

# config/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  # Offline access
  provider :shopify,
  ShopifyApp.configuration.api_key,
  ShopifyApp.configuration.secret,
  scope: ShopifyApp.configuration.scope,
  setup: lambda { |env|
    strategy = env['omniauth.strategy']

    shopify_auth_params = strategy.session['shopify.omniauth_params']&.with_indifferent_access
    shop = if shopify_auth_params.present?
      "https://#{shopify_auth_params[:shop]}"
    else
      ''
    end

    strategy.options[:client_options][:site] = shop
  }
end

Online setup <-- This works

AuthenticatedControllers work properly, but proxy app and webhooks are Unauthorized.

# config/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  # Online access
  provider :shopify,
  ShopifyApp.configuration.api_key,
  ShopifyApp.configuration.secret,
  scope: ShopifyApp.configuration.scope,
  per_user_permissions: true, # <-- this changed
  setup: lambda { |env|
    strategy = env['omniauth.strategy']

    shopify_auth_params = strategy.session['shopify.omniauth_params']&.with_indifferent_access
    shop = if shopify_auth_params.present?
      "https://#{shopify_auth_params[:shop]}"
    else
      ''
    end

    strategy.options[:client_options][:site] = shop
  }
end

Concurrent setup (offline & online) <-- How does this work?

Expected: AuthenticatedControllers, proxy app, and webhooks work properly, and you can access the Shopify user info on AuthenticatedControllers.

# config/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  # Offline access
  provider :shopify,
  ShopifyApp.configuration.api_key,
  ShopifyApp.configuration.secret,
  scope: ShopifyApp.configuration.scope,
  setup: lambda { |env|
    strategy = env['omniauth.strategy']

    shopify_auth_params = strategy.session['shopify.omniauth_params']&.with_indifferent_access
    shop = if shopify_auth_params.present?
      "https://#{shopify_auth_params[:shop]}"
    else
      ''
    end

    strategy.options[:client_options][:site] = shop
  }

  # Online access
  provider :shopify,
  ShopifyApp.configuration.api_key,
  ShopifyApp.configuration.secret,
  scope: ShopifyApp.configuration.scope,
  per_user_permissions: true,
  setup: lambda { |env|
    # ??? <-- Would this be different than the one above?
  }
end

Does this make sense? Or am I missing something obvious?

If someone has solved this in a way that is compatible with this gem, could you share it here? I'm willing to write the proper documentation once we've figured this out.

Things I've learned so far

  1. You can generate a new session using the Shopify API directly. Could adding an around_action to the AuthenticatedController that finds or creates a new online access session work? Instead of using the Omniauth::Builder?
  2. We need to toggle between the offline and online token headers depending on what kind of access a certain action requires. As seen on Shopify's OAuth instructions I believe we would have to set the proper token on each request.

Related Notes

IMO concurrent access should be the default behavior for this gem. It's one less configuration step, shouldn't break current implementations, is more secure for embedded apps, and would allow use of proxy apps, webhooks, and embedded apps with users (together) right from the get-go, which I feel is a common, and amazing, combination for apps.

Next steps

I'm opening this issue to see if I'm missing something before working on a potential solution. And if the feedback is positive, to see if anyone would like to help.

Thank you 鉂わ笍

docs

Most helpful comment

I don't have code examples for you (sorry) but I can provide some guidance. What you need to do is use the same omniauth provider with different path prefixes.

The default path prefix is /auth so all omniauth paths look like /auth/shopify, /auth/shopify/callback, etc. You can configure your provider with any path prefix you want, and omniauth will use the correct provider options.

Your config would look like

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :shopify,
    # (other options),
    per_user_permissions: true,
    path_prefix: "/online_auth`

  provider :shopify,
    # (other options),
    per_user_permissions: false,
    path_prefix: "/offline_auth`
end

Once a merchant opens your app, you would verify if your app is installed on that particular shop (Shop.find_by(shopify_domain: ...) or whatever, this is specific to your app)
1) If not installed, redirect the user to /offline_auth/shopify and this will get you an offline mode access token that you can use to subscribe to webhooks and perform background tasks for this shop. You should also keep a copy of the scopes that were granted for this access token: if you change the scopes that your app request, you should do this step again to have them granted by the merchant.
2) If your app is correctly installed (you have an offline access token), redirect the user to /online_auth/shopify and store that access token with the user info in the session object, use that access token only in response to user actions in your web app.

All 6 comments

I don't have code examples for you (sorry) but I can provide some guidance. What you need to do is use the same omniauth provider with different path prefixes.

The default path prefix is /auth so all omniauth paths look like /auth/shopify, /auth/shopify/callback, etc. You can configure your provider with any path prefix you want, and omniauth will use the correct provider options.

Your config would look like

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :shopify,
    # (other options),
    per_user_permissions: true,
    path_prefix: "/online_auth`

  provider :shopify,
    # (other options),
    per_user_permissions: false,
    path_prefix: "/offline_auth`
end

Once a merchant opens your app, you would verify if your app is installed on that particular shop (Shop.find_by(shopify_domain: ...) or whatever, this is specific to your app)
1) If not installed, redirect the user to /offline_auth/shopify and this will get you an offline mode access token that you can use to subscribe to webhooks and perform background tasks for this shop. You should also keep a copy of the scopes that were granted for this access token: if you change the scopes that your app request, you should do this step again to have them granted by the merchant.
2) If your app is correctly installed (you have an offline access token), redirect the user to /online_auth/shopify and store that access token with the user info in the session object, use that access token only in response to user actions in your web app.

And obviously you would need to change the route that handles /auth/shopify/callback to handle both of your new routes (in two controller actions)

@EiNSTeiN- Thank you! This clears things up a lot. I'll share my results once it's done.

Hi!

Is there any progress on this?
We would need to have the offline token, but at the same time see the Shopify User ID, and its other details. That is needed for security reasons - to be able to show who accessed the app, and did what, and allow app admins to set permissions for other Shopify users who are not admins.

Maris

I've seen updates happen in 11.5.1, but nothing on how to support both offline and online tokens at the same time. There's a merge request for this, but nothing has come out of it. Is there someone from the internal team on any emphasis on this? This would be an amazing feature and I'm happy to contribute if needed.

Work is currently underway for this as we are trying to implement it in our own apps as well. The first PR for per-user token support was a first step.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

quocble picture quocble  路  8Comments

wlbrough picture wlbrough  路  6Comments

jagthedrummer picture jagthedrummer  路  10Comments

robbyklein picture robbyklein  路  4Comments

MisinformedDNA picture MisinformedDNA  路  6Comments