I am trying to skip forgery protection in a pin confirmation controller but the skip forgery protection method does not seem to have an effect. The verify_authenticity_token function is always ran.
FYI skip_forgery_protection looks like:
def skip_forgery_protection(options = {})
skip_before_action :verify_authenticity_token, options
end
My controller looks like:
class PinConfirmation < Devise::ConfirmationsController
skip_forgery_protection only: :confirm_pin
def pin
# render pin confirmation page
end
def confirm_pin
# do pin confirmation
end
end
For some reason verify_authenticity_token is ran despite the fact that skip_forgery_protection is defined above.
Upon further investigation I found that after manually removing the CSRF token from the form and submitting with skip_forgery_protection both on and then off, the forgery protection is actually skipped even though the verify_authenticity_token is being ran in both cases. The skipping seems to happen at the lower level than this function.
Related
I'm wondering what would be the best way to handle the following:
I have an authentication method (used as a before_action) as follows that checks if a user_id is in the session when the login page is requested. If the user_id is detected, then it redirects the user to dashboard path.
def already_validated
if session[:uid] == user.id
redirect_to dash_path
end
end
This is leading to a too many redirect errors which I understand. I can see in pry that it's just evaluating that before_action filter every-time the page loads. That's what leads to too many redirects.
My question is what is the best way to handle this type of setup. Is there a way in rails to only evaluate on the first redirect? I thought of using a temp flag to tell if the redirect happened before. That doesn't seem very elegant though. I'm sure there is an easier/better way to manage it.
Thanks for any advice you can provide.
There has to be an exception on your before_action: you don't want to call it on the dash_path. If a user enters there and is validated, it should stay there (as what the redirect would do) and if it is not validated it should just stay there (as with any other url that fails this validation process).
There is no point on checking if it is validated as the result will always be to stay on the same page.
Then in your controller you have to specify that you want an exception on the before_action:
class SomeController < ApplicationController
before_action: :already_validated, except: [:dash_action]
def is_validated_action # the method that causes the redirect
end
def dash_action # action of dash_path url
end
def already_validated
if session[:uid] == user.id
redirect_to dash_path
end
end
end
If you want some validation before the hypothetical dash_action then create a new method for it. Be sure that you don't have circular references or it will be pretty difficult to debug on the long run.
You can just tell Rails to skip the before filter in the controller that handles the dash_path:
# in the controller
skip_before_action :already_validated
Read about Filters in the Rails Guides.
I have followed the Devise wiki on how to set up a store_user_location! method to redirect back to the previous page after sign_in/sign_out and would like to use this same method for redirecting with Pundit after a user_not_authorized is triggered but I'm not sure what to supply for the "resource_or_scope". Usually this is something Devise supplies in it's callbacks (after_sign_in_path_for(resource_or_scope)). Is it possible to use this same method with Pundit and what do I supply as the resource_or_scope?
def user_not_authorized
flash[:error] = "You are not authorized to perform this action."
stored_location_for(what_goes_here?)
end
Try to the following below, I usually approach that like this:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
# Redirects the browser to the page that issued the request (the referrer) if possible,
# otherwise redirects to the root location.
def user_not_authorized
redirect_back(fallback_location: root_path)
end
end
Hope it helps!
I found what I was looking for and the answer to "what_goes_here" is ":user", which I got to from the linked wiki's function:
def store_user_location
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
Unfortunately, this does not work like I was hoping as my before_action :store_user_location!, if: :storable_location? stores the location attempting to be accessed before it is authorized and if the user ends up being not authorized it just redirects back to the same URL they are not authorized for and an infinite redirect loop ensues.
I also tried the redirect_back fallback: root_path option and for whatever reason, it just always takes me back to the root_path, which I do not like from a usability standpoint.
I'm now looking into other options but it looks like it's going to end up just displaying a 401/404 error page when not authorized and the user can just use their browser's back button.
In my Rails project I am using Devise and I've turned on allow_unconfirmed_access in the devise initializer like this:
config.allow_unconfirmed_access_for = 1.day
This works great. If a user has not confirmed their account via the confirmation email, they can still login for 24 hours. After this, if they attempt to login, Devise handles it and gives a flash message and returns a 401, disallowing the login.
I want to be able to hook into this and add a step to auto-resend the confirmation email but I can't figure out for the life of me where to do it.
You can extend the Devise::SessionsController to add this behavior:
class Users::SessionsController < Devise::SessionsController
before_action :resend_confirmation_email_if_unsent, on: :create
def resend_confirmation_email
#user = resource # resource is a devise controller helper method
unless #user.active_for_authentication?
#user.resend_confirmation_instructions # Resets token
# Or, if you don't want to reset the tokn
# #user.send_confirmation_instructions
end
# ....
end
I know this is an old question, but I thought I might as well answer it as I was dealing with the same situation.
Anthony E's answer is almost correct, but it missed the fact that resource is not defined before the create action starts, thus resource is nil at that moment. My solution was this:
class Users::SessionsController < Devise::SessionsController
before_action :resend_confirmation_email_if_needed, on: :create
def resend_confirmation_email_if_needed
#user = resource_class.find_by_email(resource_params[:email])
unless #user.nil? || #user.active_for_authentication?
#user.resend_confirmation_instructions
end
end
end
I'm not sure if it's a good idea to retrieve the user this way. It would be much easier if super do |resource| worked for this, but it only runs upon successful login, which is not the case.
Hope this helps!
I am using ng-auth-token and devise_token_auth for authentication which is working fine. I am able to login from front end but when i visit an API url directly in browser it doesnt show any current_user. What i want to do is i want to integrate paypal checkout, so when i come back from paypal to my app after user authorization, current_user is nil and also session variable is empty (even if i set some session variable before going to paypal site).
If i add
before_action :authenticate_user!
it gives me
Filter chain halted as :authenticate_user! rendered or redirected
even if i am logged in.
I don't know how can i handle these callback response from other apps.
I found a workaround to this, but still waiting for a proper solution.
# In ApplicationController
def authenticate_current_user
head :unauthorized if get_current_user.nil?
end
def get_current_user
return nil unless cookies[:auth_headers]
auth_headers = JSON.parse cookies[:auth_headers]
expiration_datetime = DateTime.strptime(auth_headers["expiry"], "%s")
current_user = User.find_by(uid: auth_headers["uid"])
if current_user &&
current_user.tokens.has_key?(auth_headers["client"]) &&
expiration_datetime > DateTime.now
#current_user = current_user
end
#current_user
end
and use this in controllers
# In any controllers
before_action :authenticate_current_user
source: https://github.com/lynndylanhurley/devise_token_auth/issues/74
Thanks.
Addon to Ankit's solution (rep too low to comment):
This was failing for me on post requests because Rails was stripping out the cookies due to protect_from_forgery being set:
class ApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
include Pundit
protect_from_forgery with: :null_session # <-- this was the culprit
end
Removing protect_from_forgery entirely "solved" the issue, though I'm not happy with it.
The real issue (on my end, at least) is that ng-token-auth is supposed to be including the token in the header, but is only found in the cookies. My current guess is that either 1) ng-token-auth isn't properly setting its HttpInterceptor, or 2) some other interceptor is messing with it after the fact. (I've seen the ng-file-upload can cause issues, but I'm not using that...)
I have ended up with this code in ApplicationController:
before_action :merge_auth_headers
def merge_auth_headers
if auth_headers = cookies[:auth_headers]
request.headers.merge!(JSON.parse(auth_headers))
end
end
I'm using Devise in a Rails application I'm writing, and I want to let users go back to where they were after signing in or signing up.
For example, if I have a "comments" Controller that is protected by:
before_filter :authenticate_user!
Then I want users who click a "Comment Now!" button (and are therefore redirected to the new action in CommentsController) to log in and then have Devise redirect them to the new action (or wherever they were) in CommentsController, not to the generic root of the application, or to a generic after_sign_in_path.
Looking through the RDOC for Devise, I found this method that makes it look as if Devise has at least the capability to do something like this on its own, but I can't figure out a way.
OK, so I've done some more experimentation, and working with Kormie's info, I've got a working solution.
From what I can determine, before_filter authenticate_user! does not save the route for returning the user. What I did was this:
First, I added an extra before_filter at the top of my controller
before_filter :store_location
before_filter :authenticate_user!
Then, I wrote the store_location method at the bottom of the controller
private
def store_location
session[:user_return_to] = any_old_route_path
end
I don't claim this is perfect, but it works for me. (The downside for anyone else wanting to use it, is that it only supports one return path per controller. This is all I need for myself, but it is only a slight improvement over the one return path per app that I was using previously.) I would really appreciate anyone else's insights and suggestions.
Devise should do this by itself. The authenticate_user! filter also did not want to work for me when the route to the action was set via PUT method. When I have changed this to GET in routes.rb devise started to work as expected.
The simple way to do this:
# Modified from https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
stored_location_for(resource) || your_defaut_path
end
end
I think by default Devise saves the route but you may be usinging
sign_in #user
this should redirect you
sign_in_and_redirect(#user) #assuming you are sigining in that resource
Have you tried after_sign_in_path_for? If you define that method in your ApplicationController it should override the default implementation on a per controller basis.
Adapted from Devise Wiki how to:
Redirect back to current page after sign in, sign out, sign up, update
Redirecting back to the "current page" involves saving the current url in the session and then retrieving the url from the session after the user is authenticated / signed out. This should only really be done for GET requests as the other http methods (POST, PUT, PATCH, DELETE) are not idempotent and should not be repeated automatically.
To store the location for your whole application use before_action to set a callback (Use before_filter in Rails versions before 4.0).
This example assumes that you have setup devise to authenticate a class named User.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :store_user_location!, if: :storable_location?
# The callback which stores the current location must be added
# before you authenticate the user as `authenticate_user!` (or
# whatever your resource is) will halt the filter chain
# and redirect before the location can be stored.
before_action :authenticate_user!
# To redirect to the stored location after the user signs
# signs in you would override the after_sign_in_path_for method:
def after_sign_in_path_for(resource_or_scope)
# *My note, not wiki*: you may need a fall back as
# stored_location_for can return nil. I've added root_path
stored_location_for(resource_or_scope) || root_path
end
private
# Its important that the location is NOT stored if:
# - The request method is not GET (non idempotent)
# - The request is handled by a Devise controller
# such as Devise::SessionsController as that could
# cause an infinite redirect loop.
# - The request is an Ajax request as this can lead
# to very unexpected behaviour.
def storable_location?
request.get? && is_navigational_format? &&
!devise_controller? && !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
end
Reference
Devise How To: Redirect back to current page after sign in, sign out, sign up, update