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.
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.
Here's an example of what I'm talking about:
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
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
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.
around_action to the AuthenticatedController that finds or creates a new online access session work? Instead of using the Omniauth::Builder?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.
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 鉂わ笍
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.
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
/authso 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
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/shopifyand 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/shopifyand store that access token with the user info in thesessionobject, use that access token only in response to user actions in your web app.