How to rescue OmniAuth::Strategies::OAuth2::CallbackError? - ruby-on-rails

I am building a Rails application with Omniauth for log in service.To authenticate Google I am using OmniAuth Google OAuth2 Strategy.
When user clicks 'allow access' button everything works fine.But when user clicks 'no thanks' button the below error is raised.
OmniAuth::Strategies::OAuth2::CallbackError
I have tried adding the below rescue code in application controller.
class ApplicationController < ActionController::Base
rescue_from OmniAuth::Strategies::OAuth2::CallbackError, :with =>
:omniauth_callback_error_handler
protected
def omniauth_callback_error_handler
redirect_to init_sign_in_users_path
end
end
But no luck. Any idea?

You can set the on_failure proc in the omniauth initializer in an even cleaner fashion:
OmniAuth.config.on_failure = UsersController.action(:oauth_failure)

This happens because the authentication happens in a middleware so your controller is not involved in it. This is where the exception is raised and the called code is this
I think you can handle this kind of error by defining a callback in OmniAuth initializer with this kind of code
OmniAuth.config do |config|
config.on_failure do
# your handling code invoked in the context of a rack app
end
end
Otherwise there is a commit of three months ago which introduce this behavior
def redirect_to_failure
message_key = env['omniauth.error.type']
new_path = "#{env['SCRIPT_NAME']}#{OmniAuth.config.path_prefix}/failure?message=#{message_key}"
Rack::Response.new(["302 Moved"], 302, 'Location' => new_path).finish
end
which states that on errors your user is redirected to /auth/failure with an error message, so you should be able to define a route for that path and handle it in your app. Keep in mind that this won't happen in development mode so you need to try it in other envs. If this doesn't happen in production try to upgrade your omniauth gem to version 1.1.0

I have solved this problem with the Fabio's first suggestion.
OmniAuth.config.on_failure = Proc.new do |env|
UsersController.action(:omniauth_failure).call(env)
#this will invoke the omniauth_failure action in UsersController.
end
In my UsersController
class UsersController < ActionController::Base
def omniauth_failure
redirect_to init_sign_in_users_path
#redirect wherever you want.
end
end

There's a configuration to use /auth/failure instead of raising an error.
I use OmniAuth 1.2.2 and when I checking the FailureEndpoint I found the code is like this:
def call
raise_out! if OmniAuth.config.failure_raise_out_environments.include?(ENV['RACK_ENV'].to_s)
redirect_to_failure
end
And the failure_raise_out_environments is defined here:
def self.defaults
#defaults ||= {
# other configurations
:failure_raise_out_environments => ['development']
}
end
The environment can be configured so the solution is easy. I use Rails so I put below code in an initializer file:
OmniAuth.configure do |config|
# Always use /auth/failure in any environment
config.failure_raise_out_environments = []
end

Related

Spree redirect after login

Spree 2.3 with spree_auth_devise, Rails 4.0
I am trying to redirect users after sign in based on their role. The best solution would be to modify a path, a la Devise, in an initializer, but these don't seem to exist for Spree. The next best solution would be to create a decorator on the sessions controller, but I can't find this controller, nor can I access it when I attempt to follow the information from rake routes
How can I redirect users to a new location after login, in a Spree app, based on their role?
UPDATE
Overwriting the after_sign_in_path_for(resource) method results in the method being triggered, but still rerouting to the admin_path.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
byebug # this is triggered
root_path
end
end
### OR ####
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
byebug # this is triggered
if spree_current_user.has_spree_role?("admin")
admin_path
elsif spree_current_user.has_spree_role?("designer")
new_designers_spree_variant_path
else
root_path
end
end
end
My attempts are being documented here:
https://gist.github.com/asteel1981/0f258260974f4d748fb5
Thanks in advance.
Thanks to #mvidaurre for spending some time with me drilling down into this.
The issue is that spree_auth_devise has a second method that attempts to reroute to the last page attempted, meaning I needed to modify not only the after_sign_in_path_for method, but the Spree method redirect_back_or_default(default).
Additionally, because my user signs in through the admin/sign_in route, I needed to access the Spree::Admin::UserSessionsController instead of simply the Spree::UserSessionsController.
My early solution looks like this:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
if spree_current_user.has_spree_role?("admin")
admin_path
elsif spree_current_user.has_spree_role?("designer")
'/designers/spree_variants/new' #rails helper gives the wrong path, not sure why
else
root_path
end
end
end
# app/controllers/spree/admin/user_sessions_controller.rb
Spree::Admin::UserSessionsController.class_eval do
def redirect_back_or_default(default)
if spree_current_user && spree_current_user.has_spree_role?("admin")
redirect_to(session["spree_user_return_to"] || default)
session["spree_user_return_to"] = nil
else
redirect_to(default)
end
end
end
The relevant spree source code is here:
https://github.com/spree/spree_auth_devise/blob/8cb2d325b2c1da02cbe137404d8dda89cc1613a2/lib/controllers/backend/spree/admin/user_sessions_controller.rb
Thanks for your answer in the comments. spree_auth_devise defines the Spree::UserSessionsController you can decorate the controller and use the method described in: How To: redirect to a specific page on successful sign in
You can implement your custom method for:
def after_sign_in_path_for(resource)
current_user_path
end
Maybe something like this:
def after_sign_in_path_for(resource)
if spree_current_user.has_spree_role?("designer")
root_path
elsif spree_current_user.has_spree_role?("admin")
admin_path
else
root_path
end
end
Spree documentation seems to cover this, check out:
http://guides.spreecommerce.com/developer/authentication.html
It seems to be a good starting point for what you're trying to achieve.

Ruby on Rails Devise code after login

I have an RoR app using Devise for logins. There is some code that is executed when a new User record is created, by being put in the user.rb file as an after_create call/macro/whatever. I need to make this code run after each login, instead of running on new user creation.
With some Googling, it seems that one option is to place Warden callbacks in the devise.rb code. My questions are:
Is this right, and/or is there a better way to do this?
If this is the right approach ...
Should the Warden::Manager... method defs go in devise.rb inside of Devise.setup, or after it?
Is after_authentication the callback I should use? I'm just checking to see if a directory based on the user's name exists, and if not, creating it.
Just subclass Devise's sessions controller and put your custom behaviour there:
# config/routes.rb
devise_for :users, :controllers => { :sessions => "custom_sessions" }
And then create your controller like this:
# app/controllers/custom_sessions_controller.rb
class CustomSessionsController < Devise::SessionsController
## for rails 5+, use before_action, after_action
before_filter :before_login, :only => :create
after_filter :after_login, :only => :create
def before_login
end
def after_login
end
end
I found that using the Devise Warden hook cleanly allowed after login event trapping by looking for the ":set_user" :event.
In user.rb:
class User < ApplicationRecord
Warden::Manager.after_set_user do |user, auth, opts|
if (opts[:scope] == :user && opts[:event] == :set_user)
# < Do your after login work here >
end
end
end
I think this is an duplicate question. Yes you can execute code after every successful log in. you could write the code in your ApplicationController. Also have a look at http://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-on-successful-sign-in. Also, check out How to redirect to a specific page on successful sign up using rails devise gem? for some ideas.
You can do something like:
def after_sign_in_path_for(resource_or_scope)
Your Code Here
end
Reference Can I execute custom actions after successful sign in with Devise?
You could also inherit from devise session's class and use after_filter for logins.
UPDATED 2022
Warden now has built-in callbacks for executing your own code on after_authentication:
Warden::Manager.after_authentication do |user, _auth, _opts|
TelegramService.send("#{account.name} just logged in")
end
Source: https://github.com/wardencommunity/warden/wiki/Callbacks#after_authentication

Rails4 + Authlogic + rspec

Using Rails 4, I am having issues getting Authlogic to see my faked UserSession.
I have set up pages#whoami to render the current user's email address, as a simplistic test.
class PagesController < ApplicationController
# before_filter :require_user
def whoami
render :text => current_user.try(:email) || 'anonymous'
end
end
in spec/spec_helper.rb:
require "authlogic/test_case"
include Authlogic::TestCase
and my rspec test:
require 'spec_helper'
describe '/whoami' do
setup :activate_authlogic
it "should tell me who I am" do
user = FactoryGirl.create(:user)
user.should be_valid
session = UserSession.create(user)
session.should be_valid
get '/whoami'
response.body.should == user.email
end
end
I updated my application controller to show the current session:
def require_user
unless current_user
raise "Current User Session is: #{ current_user_session.inspect}"
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to new_user_session_url
return false
end
end
With before_filter :require_user commented, I correctly get "anonymous". When I uncomment it, I see that my user session is nil. I tried looking through the authlogic code but got lost in Authlogic::Session:Persistence::InstanceMethods#persisting?
I'm trying to debug. Here's where I am so far.
Here, we try to set Authlogic::Session::Base.controller to the test's mock controller:
https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/test_case.rb#L109
in my spec, I see that #controller is a Authlogic::TestCase::MockController
and in my spec, I see that Authlogic::Session::Base.controller is set to that Mock Controller.
However, I then check this:
class ApplicationController < ActionController::Base
...
def current_user_session
raise Authlogic::Session::Base.controller.inspect
...
end
end
and I see Authlogic::ControllerAdapters::RailsAdapter ... so somehow the controller is being set but isn't persisting. I'm wondering whether this has to do with the switch from Rails3 to Rails4?
Any insight into this would be appreciated.
Gem versions for those who are interested:
gem rspec-core (2.14.5)
gem authlogic (3.3.0)
gem rails (4.0.0)
Per https://stackoverflow.com/a/5803121, a request spec is just a thin wrapper around ActionDispatch::IntegrationTest. As such, there is no direct access to the session, unlike a normal controller spec.
Due to this, it isn't directly possible to log a user in directly with AuthLogic, which does rely on the session and cookies:
It first authenticates, then it sets up the proper session values and cookies to persist the session.
For request/integration/api/feature specs, a request directly to the login path will be necessary to set the proper session / cookies behind the scenes. The integration session will then be sent back (just like a normal web request) with the proper values.
To make life easier you can add a helper method, which you can include for request/integration/api/feature specs:
# spec/support/auth_logic_helpers.rb
module Authlogic
module TestHelper
# You can call this anything you want, I chose this name as it was similar
# to how AuthLogic calls it's objects and methods
def create_user_session(user)
# Assuming you have this defined in your routes, otherwise just use:
# '/your_login_path'
post user_session_path, login: user.login, password: user.password
end
end
end
# Make this available to just the request and feature specs
RSpec.configure do |config|
config.include Authlogic::TestHelper, type: :request
config.include Authlogic::TestHelper, type: :feature
end

Rails Devise Registrations redirects user after a couple of failed trials

I have a devise registrations_controller whose create method redirects the user after a couple of trials. The trouble is I am using the same create method for a dedicated registrations page and a lightbox registration. The lightbox calls the create method using an ajax call and the redirection is creating trouble.
I haven't done much in the create method other than calling super. How do I tell devise not to redirect if an ajax call made to it ?
Thanks
To prevent the redirect, you can build a custom failure app:
class CustomFailureApp < Devise::FailureApp
def respond
unless request.format.to_sym == :html
http_auth
else
redirect_to redirect_url
end
end
def http_auth
super
self.status = 401
self.content_type = 'json'
self.response_body = {
:error => 'NO MORE REDIRECT',
:status => 401
}.to_json
end
end
Configure Warden to use this failure app in the devise initializer:
Devise.setup do |config|
...
# ==> Warden configuration
# If you want to use other strategies, that are not (yet) supported by Devise,
# you can configure them inside the config.warden block.
config.warden do |manager|
manager.failure_app = CustomFailureApp
end
end
Here's a reference:
http://casperfabricius.com/site/2010/09/30/ajax-sign-in-and-sign-up-with-devise/

Devise redirect after login fail

All the questions I've found are related to a successful login with the helper
after_sign_in_path_for(resource)
I have a login form in the index of the site, and when the login fails it redirects to "users/sign_in"
But how can I redirect to my site#index when the login fails?
Create a custom_failure.rb in your lib directory, with:
class CustomFailure < Devise::FailureApp
def redirect_url
your_path
end
def respond
if http_auth?
http_auth
else
redirect
end
end
end
In your Devise initializer, include:
config.warden do |manager|
manager.failure_app = CustomFailure
end
Make sure Rails is loading your lib files, in your application.rb :
config.autoload_paths += %W(#{config.root}/lib)
Don't forget to restart your server.
I don't think there's an easier way to do this.
If you use your own SessionsController, you can re-assign the :recall value of auth_options to recall the controller#method you want before running warden.authenticate!(auth_options), for example:
in app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
#...
def create
#...
auth_options = { :recall => 'site#index', :scope => :user }
resource = warden.authenticate!(auth_options)
#...
end
#...
end
With this way, you don't need to create the customized FailureApp and modify the configs.
This is what happens with devise 3.1.0
Started POST "/users/sign_in"
Processing by Devise::SessionsController#create
Completed 401 Unauthorized
Processing by Devise::SessionsController#new
new gets called because of the auth_options defined at the end of gems/devise-3.1.0/app/controllers/devise/sessions_controller.rb
You should redefine the auth_options used in the create action. I copied the controller in app/controllers/devise/sessions_controller.rb of my Rails application and replaced the auth_options method like this
def auth_options
{ :scope => resource_name, :recall => "Home#new" }
end
It does the trick, but the url is still /users/sign_in
I'll try to fix that as well.
You can change the default sign_in path.
Check out https://github.com/plataformatec/devise/wiki/How-To:-Change-the-default-sign_in-and-sign_out-routes
Elaborating on Marcao's answer, I highly recommend placing some debugger in your CustomFailure respond method in order to better understand what is going on.
Class CustomFailure < Devise::FailureApp
def respond
binding.pry
super
end
end
If you look at the FailureApp Devise Source Code for the respond method it is super easy to understand what is going on.
def respond
if http_auth?
http_auth
elsif warden_options[:recall]
recall
else
redirect
end
end
So for example in order to return a redirect_url you would want to make sure that your respond code conditionals eventually return redirect.
However if you want to maybe return a standard 401 status as defined in the http_auth method, you want to verify that your respond method code returns http_auth.
Thus it is worth your while to look into the definition of the http_auth?
In particular, note the: request.xhr? method, which will return 0 for json requests (recall that 0 actually evaluates to true in ruby)
def http_auth?
if request.xhr?
Devise.http_authenticatable_on_xhr
else
!(request_format && is_navigational_format?)
end
end
And maybe check your initializers/devise file for config.http_authenticatable_on_xhr or config.navigational_formats in order to control the response that you want. This configuration can really affect what Devise returns and can often lead to unexpected behavior due to what it does here under the hood.

Resources