-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Mixing logging in with OmniAuth with regular OmniAuth usage
The OmniAuth: overview states at the beginning:
Remember that
config.omniauth
adds omniauth provider middleware to your application. This means you should not add this provider middleware again inconfig/initializers/omniauth.rb
as they'll clash with each other and result in always-failing authentication.
This raises the question: what if you want to use OmniAuth for more than just logging in with Devise, like accessing an API though OAuth? The answer is to not use Devise's OmniAuth helpers. These add just a very thin layer of extra functionality which you can easily take care of yourself with out-of-the-box OmniAuth functionality. This article shows you how.
We don't need any of the following:
-
:omniauthable
in the model -
config.omniauth
inconfig/initializers/devise.rb
-
devise_for :users, only: :omniauth_callbacks, controllers: { omniauth_callbacks: 'omniauth_callbacks' }
inroutes.rb
We start with the assumption that you have already created an OAuth2 app on the provider side. Make sure the app is configured with the default OmniAuth callback path /auth/google_oauth2/callback
(in case of provider google_oauth2
).
The OmniAuth provider configuration is taken care of by default in the config/initializers/omniauth.rb
file and will look something like this (in its basic form):
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET']
end
This is fine as it is and allows us to initiate the OAuth login flow with Google as well as accept the callback from Google.
Where Devise's OmniAuth implementation offers you a helper like user_google_oauth2_omniauth_authorize_path
to set the correct link for the "Log in with Google" button, we need to set a path of our own. We can simply use OmniAuth's default /auth/google_oauth2
. However, when handling the callback later on, we need to be able to distinguish a callback for the login flow from a callback for the OAuth API flow. Therefore we'll add the origin
parameter like so: /auth/google_oauth2?origin=login
.
We need to be able to handle both callbacks from the login flow as well as the OAuth API flow. We use the origin
parameter to determine to which controller we should route the callback request in routes.rb
, like so:
get '/auth/:provider/callback', to: "oauth_accounts#create_or_update", constraints: lambda { |req| !(req.env['omniauth.origin'] =~ /login/) }
get '/auth/failure', to: 'oauth_accounts#error', constraints: lambda { |req| !(req.env['omniauth.origin'] =~ /login/) }
devise_scope :user do
get '/auth/google_oauth2/callback', to: 'omniauth_callbacks#google_oauth2'
get '/auth/failure', to: 'omniauth_callbacks#failure'
end
Put the OAuth API callback routing before the login routing and check if omniauth.origin
(which contains the value of our initial URL origin
query parameter) does not contain login
. In that case, route to the controller handling the OAuth API accounts. In all other cases, we route to Devise's omniauth_callbacks controller, where we add an action for each provider.
Note that adding a constraint to the get
definition in the devise_scope
block does not seem to work. Hence we solve that by putting the OAuth API callback routing before the login routing.
From here you can follow the instructions in the OmniAuth: overview to create the appropriate controller action per provider.
Say you use OmniAuth both for logging in with Google as well as for reading the Google Calendar API somewhere in your application, then most likely you don't want to ask for Calendar read permissions right when the user logs in. You want to do that later in the OAuth API flow. You can use OmniAuth's dynamic setup phase to use a different scope for each flow, like so:
GOOGLE_SETUP_PROC = lambda do |env|
request = Rack::Request.new(env)
scope = %w[email profile]
scope << "calendar.readonly" if request.params["origin"] != "login"
env['omniauth.strategy'].options[:scope] = scope.join(",")
end
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], setup: GOOGLE_SETUP_PROC
end