I have a problem with authenticating requests made to my Rails API
In my application controller I hvae the following code
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
# protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
before_filter :authenticate_user_from_token!
private
def authenticate_user_from_token!
Rails.logger.debug "Authenticating..." <---------- (1)
authenticate_with_http_token do |token, options|
Rails.logger.debug "options: #{options.inspect}" <-----------------------(2)
user_email = options[:user_email].presence
user = user_email && User.find_by_email(user_email)
if user && Devise.secure_compare(user.authentication_token, token)
sign_in user, store: false
end
end
end
end
But the problem is that while (1) gets executed and logged, (2) is never reached for some reason. Anyone have a clue as to why this is?
Seems like your ApplicationController needs:
include ActionController::HttpAuthentication::Token::ControllerMethods
Related
I am looking for a way to check if Devise has any errors (invalid credentials etc.) for a before_action method in my ApplicationController. There is code in that method that I need only to run if Devise has no errors.
class ApplicationController < ActionController::Base
before_action :foo
def foo
if !devise_errors?
end
end
You can check credential errors like this:
class ApplicationController < ActionController::Base
before_action :foo
def foo
if !devise_errors?
end
..
private
def devise_errors?
login_params = devise_parameter_sanitizer.sanitize(:sign_in)
email = login_params.dig(:email)
password = login_params.dig(:password)
user = User.find_by(email: email)
return true if user.blank?
!user.valid_password?(password)
end
..
end
Do you mean sign in errors? Wouldn't you only need this in the session controller?
You could check the flash messages...
But you might be better off checking in Warden:
Warden::Manager.warden_callback do |user, auth, opts|
# code
end
In ApplicationController, according to devise docs, How To: Redirect to a specific page on successful sign in and sign out, the case switch when can not be reached, even in pry debugging console, it shows 'resource.class == User is true'. I don't know what part of Rails processing I missed, any hint will be appreciated!
# ApplicationController.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
protected
def after_sign_in_path_for(resource)
# check for the class of the object to determine what type it is
binding.pry
case resource.class
when User
puts "user redirect ==== "
return session.delete(:return_to) || current_user_path
else
puts "super call ....."
super
end
end
end
You are pretty close. Just need to get the resource class name using resource.class.name , so that you can compare it with a string such as 'User' which is nothing but your class name.
def after_sign_in_path_for(resource)
# check for the class of the object to determine what type it is
binding.pry
case resource.class.name #=>this would return the class name i.e 'User'
when 'User'
puts "user redirect ==== "
return session.delete(:return_to) || current_user_path
else
puts "super call ....."
super
end
end
You can make workaround by creating SessionsController that inherits from Devise::SessionsController.
class SessionsController < Devise::SessionsController
skip_before_filter :authenticate_user!
def create
user = User.find_for_database_authentication(email: params[:session][:email])
if user && user.valid_password?(params[:session][:password])
sign_in user
redirect_to session.delete(:return_to) || '/authorized'
else
redirect_to '/sign_in'
end
end
def destroy
sign_out :user
redirect_to '/signed_out'
end
end
Point to it inside your routes.rb like this:
devise_for :users, controllers: {sessions: 'sessions'}
I have a simple rails app i had added Active Admin onit, and it worked fine (except that my css is having some problem) but suddenly i started reciving this error just after i tried doing some changes to the dashboard.rb file:
This is the error:
[2016-02-16T18:56:55.902056 #5260] FATAL -- :
ArgumentError (A copy of ApplicationController has been removed from the module
tree but is still active!):
app/controllers/application_controller.rb:26:in `notification'
my application_controller.rb
class ApplicationController < ActionController::Base
include PublicActivity::StoreController
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
skip_before_filter :verify_authenticity_token
helper_method :mailbox, :conversation
before_action :set_last_seen_at, if: proc { user_signed_in? }
before_action :notification, if: proc { user_signed_in? }
private
def mailbox
#mailbox ||= current_user.mailbox
end
def conversation
#conversation ||= mailbox.conversations.find(params[:id])
end
def set_last_seen_at
current_user.update_attribute(:last_seen_at, Time.now)
end
def notification
#shipment_ids = Shipment.where(:user_id => current_user.id).pluck(:id)
#comment = Comment.where("shipment_id IN (?)", #shipment_ids).where(:is_read => false).where.not(:user_id => current_user.id)
end
end
I am implementing api via rails.
I want to implement following feature but unable to figure out how?
I tried this sample app
I have user model with email, password and access_token
class UsersController < ApplicationController
def signin
c_user = User.find_by_email(params[:email])
pass = params[:password]
if c_user.password == pass
render json: c_user.access_token
end
end
private
def users_params
params.require(:user).permit(:email, :password)
end
end
If user request via api for http://localhost:3000/signin?email=t1#t.com&password=password
then it will check email and password and return access_token that user can use for future request.
I want to implement same with devise or any other gem please help me to understand it.
Thanks in advance
This is how I emplement such mechanism in my apps:
Generate an access_token whenever a user is created.
Respond with that access_token whenever the user signs in.
Require an access_token authentication for every request needed.
user.rb
class User < ActiveRecord::Base
# Use this before callback to set up User access_token.
before_save :ensure_authentication_token
# If the user has no access_token, generate one.
def ensure_authentication_token
if access_token.blank?
self.access_token = generate_access_token
end
end
private
def generate_access_token
loop do
token = Devise.friendly_token
break token unless User.where(access_token: token).first
end
end
end
application_controller.rb
class ApplicationController < ActionController::Base
private
# To make authentication mechanism more safe,
# require an access_token and a user_email.
def authenticate_user_from_token!
user_email = params[:user_email].presence
user = user_email && User.find_by_email(user_email)
# Use Devise.secure_compare to compare the access_token
# in the database with the access_token given in the params.
if user && Devise.secure_compare(user.access_token, params[:access_token])
# Passing store false, will not store the user in the session,
# so an access_token is needed for every request.
# If you want the access_token to work as a sign in token,
# you can simply remove store: false.
sign_in user, store: false
end
end
end
Then you can use this before_filter in any controller you want to protect with access_token authentication:
before_filter :authenticate_user_from_token!
You also needs to override Devise sessions controller, so it responds with a JSON holding the access_token.
users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
# Disable CSRF protection
skip_before_action :verify_authenticity_token
# Be sure to enable JSON.
respond_to :html, :json
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource) do |format|
format.json { render json: {user_email: resource.email, access_token: resource.access_token} }
end
end
end
And make sure to specify it in your routes:
routes.rb
devise_for :users, controllers: { sessions: 'users/sessions' }
I think the reason you can't do that is because there is no password field as you were expected. There is only encrypted_password in the table.
And you must not save any user's password explicitly in a field like password for security reasons.
The way you can make your api work is by using valid_password? method provided by devise to authenticate the user.
if c_user.valid_password?(params[:password])
... ...
end
How does protect_from_forgery with: :exception work?
I'd like to edit the code to look at it and learn from it. However, I cannot find where it is placed as in a higher level of abstraction.
You can find it here on Github : https://github.com/rails/rails/blob/c60be72c5243c21303b067c9c5cc398111cf48c8/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L88
def protect_from_forgery(options = {})
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
self.request_forgery_protection_token ||= :authenticity_token
prepend_before_action :verify_authenticity_token, options
end
The with: :exception is passed to protection_method_class(:exception). Which does :
def protection_method_class(name)
ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
rescue NameError
raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
end
Then this ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify). name.to_s.classify here will be Exception.
Then you can find:
module ProtectionMethods
class Exception
def initialize(controller)
#controller = controller
end
def handle_unverified_request
raise ActionController::InvalidAuthenticityToken
end
end
end
All of this sets the way the invalid authenticity will be handled.
Then it sets a before_action: :verify_authenticity_token.
def verify_authenticity_token
unless verified_request?
logger.warn "Can't verify CSRF token authenticity" if logger
handle_unverified_request
end
end
Which uses the previously defined strategy:
def handle_unverified_request
forgery_protection_strategy.new(self).handle_unverified_request
end
To raise the exception as defined in Exception.