Devise warden 401 Unauthorized when wrong credentials - ruby-on-rails

I have a quite standard Devise login procedure with:
View:
resource_name, :url => session_path(resource_name)) do |f| %>
<%= f.input :password, input_html: {class: "span6"} %>
<% if devise_mapping.rememberable? -%>
<p><%= f.check_box :remember_me %> Remember me</p>
<% end -%>
<input type="hidden" name="after_sign_in_page" value="<%=#after_sign_in_page%>">
<p><%= f.submit "Sign in", class: "btn btn-success" %></p>
And I just created a sessioncontroller to downcase the email:
class SessionsController < Devise::SessionsController
def create
params[:user][:email].downcase!
super
logger.debug "Errors: #{resource.errors}"
end
A login with good credentials happens fine.
With wrong credentials, It redirects to the sign-in page with this log:
Started POST "/users/sign_in" for 127.0.0.1 at 2013-01-10 09:59:44 +0100
Processing by SessionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"8eytQkr20JOOOdDvpCWakbmUzNoaHMxK9/BSEVxETik=", "user"=>{"email"=>"nicolas#demoreau.be", "password"=>"[FILTERED]", "remember_me"=>"0"}, "after_sign_in_page"=>"", "commit"=>"Sign in"}
Time zone: (GMT+00:00) UTC, current area: , user to register: , current controller: sessions
Completed 401 Unauthorized in 69ms
Processing by SessionsController#new as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"8eytQkr20JOOOdDvpCWakbmUzNoaHMxK9/BSEVxETik=", "user"=>{"email"=>"nicolas#demoreau.be", "password"=>"[FILTERED]", "remember_me"=>"0"}, "after_sign_in_page"=>"", "commit"=>"Sign in"}
Rendered devise/sessions/_new.html.erb (17.8ms)
Rendered devise/sessions/new.html.erb within layouts/application (19.7ms)
Rendered layouts/_header.html.erb (66.1ms)
Completed 200 OK in 173ms (Views: 98.3ms | ActiveRecord: 0.9ms)
Apparently the 401 is dropped by Warden but I couldn't figure out why.
The user is correctly redirected back to the login page but there is no error message displayed (which is normal as they are wiped out by the redirect)
What am I doing wrong?
thanks!
EDIT 1:
For now, I found a quick hack. I added this in SessionsController#new
if params[:user]
flash[:alert] = "Incorrect login or password"
end
Not very elegant but at least, I have something.

First of all, let me advice you against overriding Devise controllers:
In this case, Devise takes care of transforming the email to lower
case for you, so there's really no need to overwrite the create method.
Your app will support Devise updates seamlessly if you stick to the
standard.
Also, Devise should set a flash error automatically, make sure you're displaying it in your view.
The status code 401 is just a standard response for unauthorized requests.
401 Unauthorized is similar to 403 Forbidden, but specifically for use
when authentication is required and has failed or has not yet been
provided
http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
You should definitely consider dropping your custom controller,
Cheers

Your flash message is not going to be set because Devise::SessionsController#create calls warden for the authentication which, in case of failures, will call Devise::FailureApp. Your controller never handles the failure scenarios.
If you want a custom message, you can customize the failure app for that, there are some articles in the Devise wiki explaining how to do so.
But in general, you can customize your failure messages via I18n and there is probably a better way to achieve what you want without a need to override Devise controllers.

I agree with jassa, just update your Devise version (with bundle update devise).
Case insensitive emails are already present in Devise, just make sure you have this config:
# devise.rb
Devise.setup do |config|
config.case_insensitive_keys = [:email ]
end
In any case, since you seem to be missing some flash messages and this config, perhaps it would better if you just re-ran the generator:
rails generate devise:install
You should then let Devise overwrite some files, just make sure you backup yours first.

Related

Rails Devise-omniauth route points to passthru instead of twitter

I am trying to implement omniauth-twitter with Devise in Ruby-on-Rails with no success.
According to the definitive article of Devise, the link
<%= link_to "Sign up with twitter", user_twitter_omniauth_authorize_path,
method: :post %>
should take the visitor to Twitter (Note I have disabled turbo site-wide with Turbo.session.drive = false and so turbo is irrelevant).
However, it just displays
Not found. Authentication passthru.
I notice the route looks wrong in the first place:
% bin/rails routes -g omni
Prefix Verb URI Pattern Controller#Action
user_twitter_omniauth_authorize GET|POST /users/auth/twitter(.:format) users/omniauth_callbacks#passthru
user_twitter_omniauth_callback GET|POST /users/auth/twitter/callback(.:format) users/omniauth_callbacks#twitter
It apparently points to Users::OmniauthCallbacksController#passthru, which is a non-existent method, hence the error?
This problem has been reported multiple times in Stackoverflow and Github (e.g., Github, SO1, SO2). A general advice seems to be using POST as opposed to GET. However, besides it is definitely POST in my case (as confirmed with a log file), I doubt if it is relevant to the routes!
What is the probable cause of this probblem and how can I solve it?
I confirm I am using OA1 API keys as opposed to Twitter OA2 (the latter is not supported by Omniauth, yet).. Also, as the doc of omniauth-twitter suggests, I confirm that "User authentication set up" is active in the Twitter Dev center to allow users to log in.
My relevant files are as follows:
# Gemfile
gem 'devise'
gem 'devise-i18n'
gem 'omniauth', '~> 2.1' #, '1.9.1'
gem 'omniauth-twitter'
gem 'omniauth-rails_csrf_protection'
# /config/initializers/devise.rb
Devise.setup do |config|
config.omniauth :twitter, 'MY_APP', 'MY_SECRET'
OmniAuth.config.logger = Rails.logger if Rails.env.development? # for debug
end
Note there are no Omniauth or Twitter-related configs in config/initializers/. Just devise.rb.
# /models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable, #...snipped...
:omniauthable, omniauth_providers: %i(twitter)
def self.from_omniauth(auth)
find_or_create_by(provider: auth.provider, uid: auth.uid) do |user|
user.display_name = auth.info.nickname.strip
user.skip_confirmation!
end
end
end
# /app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token, only: [:twitter]
def twitter
callback_from __method__
end
def callback_from(provider)
# See User model
#user = User.from_omniauth(request.env['omniauth.auth'])
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: provider.to_s.capitalize) if is_navigational_format?
else
session["devise.#{provider.to_s}_data"] = request.env['omniauth.auth'].except(:extra)
redirect_to new_user_registration_url(from_omniauth_callback: true)
end
end
def failure
redirect_to root_path
end
end
# /config/routes.rb
Rails.application.routes.draw do
devise_for :users, except: [:destroy],
controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
end
View (the turbo-part is meaningless, for it is globally turned off).
<%# /app/views/devise/registrations/new.html.erb >
<%= link_to t(".sign_up_with_twitter"), user_twitter_omniauth_authorize_path,
method: :post, data: { turbo: false } %>
The standard output of the server (bin/dev in Rail 7):
00:... | I, [2022-...6 #793] INFO -- : Started POST "/en/users/auth/twitter" for 127.0.0.1 at 2022-11-27 00:33:14 +0000
00:... | I, [2022-...4 #793] INFO -- : Processing by Users::OmniauthCallbacksController#passthru as HTML
00:... | I, [2022-...0 #793] INFO -- : Parameters: {"authenticity_token"=>"[FILTERED]", "locale"=>"en"}
00:... | D, [2022-...0 #793] DEBUG -- : Rendering text template
00:... | I, [2022-...7 #793] INFO -- : Rendered text template (Duration: 0.0ms | Allocations: 10)
00:... | I, [2022-...9 #793] INFO -- : Completed 404 Not Found in 4ms (Views: 2.5ms | ActiveRecord: 0.0ms | Allocations: 1170)
Version information
omniauth-rails_csrf_protection (0.1.2) → (1.0.1)
Gemfile did not specify the version and yet a lower-version was installed. I now bundle install with '~> 1.0'. The same error still remains (after server-restart).
omniauth (2.1.0)
omniauth-oauth (1.2.0)
omniauth-twitter (1.4.0)
devise (4.8.1)
rails (7.0.4)
ruby (3.1.2)
That's it. Thank you.
This isn't a full answer, but more of a suggestion of where to keep looking.
You are supposed to see (twitter) Request phase initiated. in the logs immediately following Started POST "/users/auth/twitter" (from here).
But, the Omniauth controller is instead looking for an HTML template to render (and failing to find one).
INFO -- : Started POST "/en/users/auth/twitter" for 127.0.0.1 at 2022-11-27 00:33:14 +0000
INFO -- : Processing by Users::OmniauthCallbacksController#passthru as HTML
INFO -- : Parameters: {"authenticity_token"=>"[FILTERED]", "locale"=>"en"}
DEBUG -- : Rendering text template #<--- HERE!!!!
INFO -- : Rendered text template (Duration: 0.0ms | Allocations: 10)
INFO -- : Completed 404 Not Found in 4ms (Views: 2.5ms | ActiveRecord: 0.0ms | Allocations: 1170)
This does make sense; because Users::OmniauthCallbacksController#passthru is getting the POST request as HTML, it's looking for an HTML template to render (and failing to find one).
It seems like Omniauth expects an AJAX request as JSON, not an HTML request.
A few thoughts:
Turn Turbo back on and see what the content-type of the request is when Turbo gets to decide
Ditch the user_twitter_omniauth_authorize_path in favor of intercepting the link click and forming an AJAX POST request using Stimulus (Rails 7) or Javascript directly to the path /users/auth/twitter.
The language "en" in the URL of the POST is probably coming from some other gem (devise-i18n?) That could be interfering with the POST request somehow and turning the content-type to HTML instead of JSON.

ActionController::InvalidAuthenticityToken on updating the password

I have a Ruby On Rails application. Now, I started getting ActionController::InvalidAuthenticityToken error while updating password in admin_controller.
CSRF token is present in layout.
Earlier it was working, today when I get a warning from google to change password, I tried to update the password & got this error.
Below is the request:
Started PATCH "/admin/password/change" for 127.0.0.1 at 2020-07-25 22:05:38 +0530
Processing by Admin::PasswordsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"osXhNhqJZ9qXeJ4F2BXrJvOTflrG5G3MGPl7yuOa4Y8PoqIXKEVe17bqO5u9nGYG2Bn0Zun2U9mOR4/uxNajsg==", "current_password"=>"[FILTERED]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}
I am using devise-4.3 for authentication.
If tried to update the password 3-4 time, then it works but not each time.
I believe I should refresh the token, turbolinks might be creating an issue.
Every other post/patch request is working.
Ruby-2.4.0, Rails-5.1.4
Go to the controller that’s generating the error, and paste following line of code above all the defined functions:
skip_before_filter :verify_authenticity_token, :only => :create
OR
protect_from_forgery with: :null_session
save it then restart the server!
Let me know if this works for you!
Need to Hard reload the page/disable turbolinks, so I added the following in link_to
<%= link_to 'Change Password', change_admin_password_path, data: { turbolinks: false }) %>
Now the complete page reload is happening and I am able to update the password.

Request in Capybara a GET, when it should be a POST

So this is a pretty basic Rails 5 application using Docker, which includes some user authentication that I built from scratch (not using Devise etc). Now, I want to start learning about request specs with Capybara, but I'm hitting what seems like a pretty strange issue with it.
Here's my login form (sessions.new.erb):
<%= form_tag sessions_path do %>
<form class="m-t" role="form" action="/">
<div class="form-group">
<%= text_field_tag :email, params[:email], class: 'form-control', placeholder: "Email Address", required: "" %>
</div>
<div class="form-group">
<%= password_field_tag(:password, nil, class: 'form-control', placeholder: "Password", required: "") %>
</div>
<div class="form-group">
<%= check_box_tag :remember_me, 1, params[:remember_me] %>
<%= label_tag :remember_me %>
</div>
<div class="actions"><%= submit_tag "Log In", class: "btn btn-primary block full-width m-b" %></div>
</form>
<% end %>
And my requests/sessions_spec.rb:
require "rails_helper"
RSpec.feature "Login", :type => :feature do
scenario "handles wrong email and password gracefully" do
visit login_path
fill_in "Email Address", :with => "something"
fill_in "Password", :with => "something"
click_button "Log In"
expect(page).to have_text("Email or password incorrect")
end
end
Now, this works if you test it manually so I would presume Capybara would see the same thing. But it kept failing. I've got the application configured so that if you try and access a protected controller and you're not logged in, it redirects you to /login and flashes a message to say Please log in to see this page. The Rspec test was returning that, which was weird - that suggested that Capybara was trying to visit another page.
So I tailed the test logs (docker-compose run web tail -f log/test.log)
And what I found is puzzling me:
Started GET "/login" for 127.0.0.1 at 2017-10-17 06:59:26 +0000
Processing by SessionsController#new as HTML
Rendering sessions/new.html.erb within layouts/empty
Rendered sessions/new.html.erb within layouts/empty (1.1ms)
Completed 200 OK in 6ms (Views: 6.1ms | ActiveRecord: 0.0ms)
Started GET "/?email=something&password=[FILTERED]&commit=Log+In" for 127.0.0.1 at 2017-10-17 06:59:26 +0000
Started GET "/locations" for 127.0.0.1 at 2017-10-17 06:59:26 +0000
Processing by LocationsController#index as HTML
Redirected to http://www.example.com/login
Filter chain halted as :authenticate rendered or redirected
Completed 302 Found in 2ms (ActiveRecord: 0.0ms)
Started GET "/login" for 127.0.0.1 at 2017-10-17 06:59:26 +0000
Processing by SessionsController#new as HTML
Rendering sessions/new.html.erb within layouts/empty
Rendered sessions/new.html.erb within layouts/empty (1.1ms)
Completed 200 OK in 6ms (Views: 4.9ms | ActiveRecord: 0.0ms)
(0.4ms) ROLLBACK
The first bit is okay, GET Login is processed by the SessionsController#new. But then, (see line 6) for some reason Capybara tried to GET the root URL, passing the email/password params in. My root URL is mapped to the LocationsController#index, which the user isn't allowed to access, so gets redirected back to /login with the message Please log in to see this page. What that button actually does is send a POST to SessionsController#create. And if you watch the logs when you do it manually, that's exactly what happens:
web_1 | Started POST "/sessions" for 172.18.0.1 at 2017-10-17 07:02:19+0000
web_1 | Processing by SessionsController#create as HTML
I can't work out why in Capybara when you press the button it performs a completely different request to when you click the button manually.
Help greatly appreciated!
A couple of clarifications first.
You're not writing request specs, you're writing feature specs (as evidenced by the use of RSpec.feature and :type => :feature
You don't need to specify :type => :feature when using RSpec.feature since that's already set.
Now on to your issue. You have nested forms in your view code since form_tag creates a <form>element and then you have another <form> element directly inside that (Note: it's always better to post the actual HTML rather than the erb so people can see what the actual HTML is). Combine that with the fact you appear to be using the rack-test driver (no js: true metadata) which won't behave the same way as a real browser when the HTML is invalid (which nested forms are), and you end up with your current behavior. I would guess when you use it with a real browser the inside form element is ignored and the outer form element has a method attribute equal to "post" so it gets posted. When using rack-test it's probably submitting the inner form element which has no method attribute and therefore defaults to "get". Remove the extraneous <form class="m-t" role="form" action="/"> form element from your view and things should work.

rails 3.1.0 devise with cancan getting unauthorized with invalid username and/or password

I have a fairly simple app using devise and cancan for authentication and authorization. Everything works great except when users try signing in with invalid usernames and/or passwords. When this happens, we get an error loading page with the following exception in the logs:
Started POST "/users/sign_in" for 127.0.0.1 at 2012-02-09 22:23:22 -0600
Processing by Devise::SessionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"blahblahblahblah", "user"=>{"login"=>"asdasd", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Sign in"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE (lower(username) = 'asdasd' OR lower(email) = 'asdasd') LIMIT 1
Completed 401 Unauthorized in 74ms
I'm not sure what I need to set to allow the authorization and/or how to get more detailed logs to see exactly what is not authorized? If I enter valid credentials I can access the application and all other pieces of the app work as expected.
I know this question has been posted a couple months ago but I hot the same issue, and after fighting it for a few hours, overwriting Devise SessionController, Devise Custom Failure, debugging through, etc.., I finally found out what was going on and I decided to share the solution so you guys won't have to go through that.
The error 'Completed 401 Unauthorized in XXms' happens within the create method of SessionController at the line:
resource = build_resource(...)
And the resource cannot be built since the resource is not passed to the server. Now the problem resides in WHY the resource isn't passed to the server? I'm using JQUERY mobile which post the sign_in as an AJAX call, which JQUERY cannot upload data like that through AJAX.
You need to add to your signin form the data-ajax=false:
in Devise/sessions/new.html.erb modify the form to look like this:
form_for(resource, :as => resource_name, :url => user_session_url, html: {data: {ajax: false}}) do |f|
Hope that helps someone down the road.

Why is the wrong action processing my .ajax request.

This is an issue I have been working around for some time now thinking I would eventually stumble on an explaination. I have not and it's now becoming a little more problematic for me.
I used rails-generate-scaffold to create a simple (users and posts) application. This was done after installing jquery and jquery-ui. I added actions "login" and "auth" to the users controller. Login.html.erb contains some javascript that sends .ajax request to the users#auth action passing the login information (email and password) as parameters.
The the template auth.js.erb exists. The "auth" action responds to format.js. The request looks normal, but rails processes the request with the "show" action rather than the "auth" action.
In other words, a request to userscontrollers#auth (via .ajax) is being processed by userscontroller#show (as JS).
The problem goes away if I remove the "resources :users" route that scaffold added (the correct action is then called). But without this route other useful scaffold stuff becomes unuseable (like users#new).
From Gemfile: gem 'jquery-rails', '>=0.2.6'
Installed jQuery with: rails generate jquery:install --ui
From ../layouts/application.html.erb
<head>
<title>Tab1</title>
<%= stylesheet_link_tag :all %>
<%= stylesheet_link_tag 'jquery-ui-1.8.16.custom.css' %>
<%= javascript_include_tag :defaults %>
<%= javascript_include_tag 'jquery.min.js' %>
<%= javascript_include_tag 'jquery.min.js', 'jquery-ui.min.js' %>
<%= csrf_meta_tag %>
</head>
Here is ./log/development.log to shows the request and the response.
Started GET "/users/auth/?email=steve&password=[FILTERED]&_=1326063255777" for 24.11.242.181 at 2012-01-08 17:53:45 -0500
Processing by UsersController#show as JS
Parameters: {"email"=>"steve", "password"=>"[FILTERED]", "_"=>"1326063255777", "id"=>"auth"}
^[[1m^[[36mUser Load (0.2ms)^[[0m ^[[1mSELECT `users`.* FROM `users` WHERE `users`.`id` = 0 LIMIT 1^[[0m
Completed 404 Not Found in 6ms
ActiveRecord::RecordNotFound (Couldn't find User with ID=auth):
app/controllers/users_controller.rb:43:in `show'
Suggests that the request --> GET "/users/auth/?email ... is being processed by UsersController#show as JS
Thanks

Resources