I'm trying to add some custom code to make the user/shop re-authenticate when the scope changes.
It's related to this point from #350:
Keep track of extra information returned by Shopify, such as scope & associated user. Currently this app has no way to keep track of this, it only keeps the access token. Keeping track of the scope would facilitate the workflow when updating the application's requested scope. We should force a new oauth autorization when the needed access scope change.
Here's a little code snippet:
current_scope = ShopifyAPI::AccessScope.all.map(&:handle)
new_scope = ShopifyApp.configuration.scope.split(', ')
if current_scope.sort != new_scope.sort
# what to do here?
end
I'm stuck as to how to redirect to re-auth path. I've tried all of these (and several more):
set_esdk_headers # response.set_header('P3P', 'CP="Not used"'); response.headers.except!('X-Frame-Options')
fullpage_redirect_to "/auth/shopify?shop=#{@shop.shopify_domain}"
fullpage_redirect_to "https://#{@shop.shopify_domain}/admin/oauth/authorize?client_id=#{ENV['SHOPIFY_API_KEY']}&redirect_uri=...&response_type=code&scope=write_themes%2Cread_orders&state=#{? SecureRandom.hex ?}"
params[:shop] = @shop.shopify_domain
close_session
session[:shopify] = nil
session[:shopify_domain] = nil
redirect_to_login
fullpage_redirect_to "/auth/shopify?shop=#{@shop.shopify_domain}"
What I'm trying to do is logout user and redirect him to reauthenticate. If it's not needed, we could skip logout. But I'm generally stuck as to where to redirect to. I understand this might not be shopify_app specific question, but omniauth / oauth specific question. I'm also going through the omniauth documentation, but I still can't figure out what should the redirect url be.
I restarted the app after changing the scope so my thought was that redirecting to /auth/shopify would trigger new scope.
There is only one way to ask for permissions: https://help.shopify.com/api/getting-started/authentication/oauth#step-2-ask-for-permission whether you ask for the the first time or 100th time. You can ask for more or less scopes in subsequent authorizations.
@EiNSTeiN- THANKS!!! I got it working finally. :)
@vfonic - what did you end up doing?
Hey @davidwparker!
I created a new request, kind-of like on the first app install:
https://{shop}.myshopify.com/admin/oauth/authorize?client_id={api_key}&scope={scopes}&redirect_uri={redirect_uri}&state={nonce}&grant_options[]={access_mode}
Here's the code snippet:
set_esdk_headers # from shopify_app gem
session['omniauth.state'] = current_shop.scope_nonce # you need to set some state/nonce that you can read later to confirm that's that request
url = "https://#{current_shop.shopify_domain}/admin/oauth/authorize"
request_params = []
request_params << "client_id=#{ENV.fetch('SHOPIFY_API_KEY')}"
request_params << "scope=#{ERB::Util.url_encode(new_scope.join(','))}"
request_params << "redirect_uri=https://#{ENV.fetch('APP_DOMAIN')}/auth/shopify/callback"
request_params << "state=#{current_shop.scope_nonce}"
fullpage_redirect_to "#{url}?#{request_params.join('&')}"
current_shop is my own helper for getting the access to the...current logged in shop obviously :)
This solved my issue too, thanks for posting your solution!
@vfonic Thanks so much for this code snippet, I've couple of questions,
omniauth.state?same_site_cookies? My app won't load because of this oauth_error=same_site_cookiesHey @milkbottlelough care to share the steps of making this work for you? I'm stuck on this and I'd really appreciate help on this 馃槄 TIA 馃檹
Here's the code snippet that I use for updating scopes:
set_esdk_headers
session['omniauth.state'] = shop.nonce
url = "https://#{shop.shopify_domain}/admin/oauth/authorize"
request_params = []
request_params << "client_id=#{ENV.fetch('SHOPIFY_API_KEY')}"
request_params << "scope=#{ERB::Util.url_encode(new_scope)}"
request_params << "redirect_uri=https://#{ENV.fetch('APP_DOMAIN')}/auth/shopify/callback"
request_params << "state=#{shop.nonce}"
fullpage_redirect_to "#{url}?#{request_params.join('&')}"
Use this with an "authenticated controller", the one that has: include ShopifyApp::Authenticated or inherits from AuthenticatedController
EDIT: I just realized I already posted this. :) I don't know any more details. I haven't had the need to change the scope for several years now. :) But that's the main part: you have to redirect back to Shopify informing Shopify that user needs to approve the new scope.
@vfonic Thanks so so much, I really appreciate your comment 馃檹鉂わ笍
It's working now! I guess the code wasn't in the right place.
Here is my setup based on your snippet:
SettingsController is my root controller.
class SettingsController < AuthenticatedController
before_action :set_shop
before_action :check_scope_and_reauth
...
def check_scope_and_reauth
shop_remote_scope = ShopifyAPI::AccessScope.find(:all).map(&:handle).sort
app_scope = ShopifyApp.configuration.scope.split(',').sort
if shop_remote_scope != app_scope
set_esdk_headers
session['omniauth.state'] = @shop.scope_nonce
url = "https://#{current_shop.shopify_domain}/admin/oauth/authorize"
request_params = []
request_params << "client_id=#{ENV.fetch('SHOPIFY_APP_API_KEY')}"
request_params << "scope=#{ERB::Util.url_encode(app_scope)}"
request_params << "redirect_uri=https://#{ENV.fetch('HOST_URL')}/auth/shopify/callback"
request_params << "state=#{@shop.scope_nonce}"
fullpage_redirect_to "#{url}?#{request_params.join('&')}"
end
end
...
end
What I'm still not sure about is the state/nonce, I'm not doing anything to check on this yet the code works, am I missing something? Or does shopify_app gem magically takes care of it? 馃
I can't get this to work anymore now with the same site cookies update from Chrome earlier this year. Does anyone know what else you need to get this to work?
Been blowing hours on this 馃槀 would love a tip of wisdom from someone that cracked the code.
Figured out that the shopify app will automatically redirect to the auth page with the new scopes - if the current session expires.
This lead me to the work around below. Inspired by you guys. Not battle-tested yet, it might be relevant tho.
def check_scope_and_reauth
return unless current_shop
shop_remote_scope = ShopifyAPI::AccessScope.find(:all).map(&:handle).sort
app_scope = ShopifyApp.configuration.scope.split(',').sort
if shop_remote_scope != app_scope
shop = Shop.find_by(shopify_domain: current_shop.shopify_domain)
shop.shopify_token = nil
shop.save
fullpage_redirect_to("/login?shop=#{shop.shopify_domain}")
end
end
Figured out that the shopify app will automatically redirect to the auth page with the new scopes - if the current session expires.
This lead me to the work around below. Inspired by you guys. Not battle-tested yet, it might be relevant tho.
def check_scope_and_reauth return unless current_shop shop_remote_scope = ShopifyAPI::AccessScope.find(:all).map(&:handle).sort app_scope = ShopifyApp.configuration.scope.split(',').sort if shop_remote_scope != app_scope shop = Shop.find_by(shopify_domain: current_shop.shopify_domain) shop.shopify_token = nil shop.save fullpage_redirect_to("/login?shop=#{shop.shopify_domain}") end end
This is great. I actually don't think you even need to delete the shopify_token though. The login URL seems to still work.
def check_scope
@shop_remote_scope = ShopifyAPI::AccessScope.find(:all).map(&:handle).sort
@app_scope = ShopifyApp.configuration.scope.split(',').sort
@shop_remote_scope != @app_scope ? @out_of_date_scope = true : @db_shop.update(updated_scope: DateTime.now)
if @out_of_date_scope
@update_scope_URL = "Xxxx/login?shop=#{@db_shop.shopify_domain}&redirect_back=/setup&update_scope=true" if Rails.env.development?
@update_scope_URL = "Xxxxx/login?shop=#{@db_shop.shopify_domain}}&redirect_back=/setup&update_scope=true" if Rails.env.production?
end
end
Then because I'm using JWT tokens with Turbolinks, I render this is in my js.erb file to redirect.
<% if @update_scope_URL %>
top.location.href="<%= @update_scope_URL %>"
<% end %>
Something that caught me out: In your shopify_app.rb file, you can't have any spaces between your commas and scopes or shop_remote_scope and app_scope won't be the same.
@oliwoodsuk Neat!
Keep in mind that the solution taps into your request quote - so make sure not to check on every incoming request. I learned this the hard way 馃槄
Most helpful comment
Hey @davidwparker!
I created a new request, kind-of like on the first app install:
Here's the code snippet:
current_shopis my own helper for getting the access to the...current logged in shop obviously :)