I have a link in my application.html.erb file
<%= link_to 'Sprzedaż', sell_path, remote: true %>
In the controller I authenticate user with before_action :authenticate_user!. Below is my authenticate_user! method.
protected
def authenticate_user!
if user_signed_in?
super
else
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
end
Basically it works correctly if the user isn't authorized. If the user has enabled Javascript it shows nice notification, and if the user hasn't enabled Javascript it shows alert and redirect to root_path which is good. The problem is that when the user is signed in and click the link nothing happens. It should redirect to the sell_path.
This is my ItemsController
class ItemsController < ApplicationController
before_action :authenticate_user!
def sell
#user = current_user
#items = JSON.parse(HTTParty.get("http://steamcommunity.com/profiles/#{#user.uid}/inventory/json/730/2?l=polish").body)
end
end
This is my ApplicationController
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
before_action :steam_informations
def steam_informations
#steam = session[:steam]
end
protected
def authenticate_user!
if user_signed_in?
super
else
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
end
end
You are trying to override helpers which defined in runtime. This is not how you must to do it.
In your case I recommend you to define for example authenticate! method like this:
def authenticate!
return true if user_signed_in?
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
This method will do nothing if user signed in and redirect to root page if user not signed in. Just define this method in ApplicationController and then use before_filter :authenticate! hook to execute it.
When before_filter \ before_action method returns false - rails won't execute your action. And if return true or just return - rails will execute action after hooks like authenticate! and render your views. To make it clear I'll show you some examples.
class FooController << ApplicationController
before_filter :dead_hook, only: :index
before_filter :nice_hook, only: :show
def index
# this action will be never executed because dead_hook method returns false.
end
def show
# this action will be executed right after nice_hook method because of 'return true' command in nice_hook method
end
def dead_hook
return false
end
def nice_hook
return true
end
end
Another way to do just like you trying to do - monkey-patch devise helper. You can do it like this:
module Devise
module Controllers
module Helpers
def authenticate_user!
# implement your logic here
end
end
end
end
Here you can check out whats going on in devise helpers:
Github Devise Helpers source code
Just for clarification: there is no difference between before_filter and before_action. Feel free to use any of them. before_action newer but before_filter not deprecated.
Related
I am working on rails and trying to make a simple blog site and its working the way i want to on my local machine but when pushed to production its being blocked by the callback functions.
My before_action :authorized_user? callback is being called and it prompts for logging if not logged in for performing any method on the blog , and if logged in all methods create, update and destroy methods are working perfectly in my development environment but in production even after the user is logged in also and when the create method is being called it asks for to log in . I am unable to understand from where or what code is causing this to happen because the same is working perfectly fine on local machine.
Any help will he highly appreciated.
My blog_controller.rb file is
class BlogsController < ApplicationController
before_action :set_blog, only: [:show, :update, :destroy, :lock_blog, :pin_blog]
before_action :authorized_user?, except: [:index, :show]
def index
#blogs = Blog.all
render json: { blogs: #blogs },status: :ok
end
def show
comments = #blog.comments.select("comments.*, users.username").joins(:user).by_created_at
render status: :ok, json: { blog: #blog, blog_creator: #blog.user, comments: comments }
end
def create
#blog = Blog.new(blog_params.merge(user_id: #current_user.id))
if authorized?
if #blog.save
render status: :ok,
json: {blog: #blog , notice: "Blog Successfully created"}
else
errors = #blog.errors.full_messages.to_sentence
render status: :unprocessable_entity, json: {error:errors}
end
end
end
def update
if authorized?
if #blog.update(blog_params)
render status: :ok,
json: {blog: #blog, notice:"Blog successfully updated"}
else
render status: :unprocessable_entity,
json: {errors: #blog.errors.full_messages.to_sentence}
end
else
handle_unauthorized
end
end
def destroy
if authorized?
if #blog.destroy
render status: :ok,
json: {notice:'Blog deleted'}
else
render status: :unprocessable_entity,
json: {errors: #blog.errors.full_messages.to_sentence}
end
else
handle_unauthorized
end
end
private
def set_blog
#blog = Blog.find(params[:id])
end
def blog_params
params.require(:blog).permit(:title,:body,:image,:is_pinned, :is_locked)
end
def authorized?
#blog.user_id == #current_user.id || #current_user.admin_level >= 1
end
def handle_unauthorized
unless authorized?
render json:{notice:"Not authorized to perform this task"}, status:401
end
end
end
and application_controller.rb file is
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
include CurrentUserConcern
include ExceptionHandlerConcern
include TokenGenerator
def authorized_user?
render json: { notice: 'Please log in to continue' }, status: :unauthorized unless #current_user
end
def authorized_admin?
authorized_user?
render json: {errors: 'Insufficient Administrative Rights'}, status: 401
end
private
end
current_user_concern.rb file
module CurrentUserConcern
extend ActiveSupport::Concern
included do
before_action :set_current_user
end
def set_current_user
if session[:token]
#current_user = User.find_by(token: session[:token])
end
end
end
Its generally recommended to use libraries for authentication and authorization instead of reinventing the wheel unless its for learning purposes. They have many eyes looking for bugs and insecurites and are battle hardened by tons of users. Home-rolled authentication systems are a very common source of security breaches which could lead to very expensive consequences.
If you're going to roll your own authorization and authentication solution I would suggest you take a page from the libraries like Devise, Pundit and CanCanCan and raise an error when a user is not authorized or authenticated so that you immediately halt whatever the controller is doing and stop the callback chain from executing further.
# app/errors/authentication_error.rb
class AuthenticationError < StandardError; end
# app/errors/authorization_error.rb
class AuthorizationError < StandardError; end
# app/controllers/concerns/
module Authenticable
extend ActiveSupport::Concern
included do
helper_method :current_user, :user_signed_in?
before_action :authenticate_user
rescue_from AuthenticationError, with: :handle_unauthorized
end
def current_user
#current_user ||= find_user_from_token if session[:token].present?
end
def find_user_from_token
User.find_by(token: session[:token])
end
def user_signed_in?
current_user.present?
end
def authenticate_user
raise AuthenticationError.new('Please log in to continue') unless user_signed_in?
end
def handle_unauthenticated(error)
render json: {
notice: error.message
},
status: :unauthorized
end
end
end
# app/controllers/concerns/authorizable.rb
module Authorizable
extend ActiveSupport::Concern
included do
rescue_from AuthenticationError, with: :handle_unauthorized
end
def authorize_admin
raise UserAuthenticationError.new('Insufficient Administrative Rights') unless current_user.admin?
end
def handle_unauthorized(error)
render json:{
notice: error.message
}, status: :unauthorized
end
end
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
include Authenticable
include Authorizable
# Should you really be mixing this into the controller? Seperate the responsibilites!
include TokenGenerator
end
It also makes debugging much easier as you can disable rescue_from in testing so that you get an exception instead of just a cryptic failure message.
You should also setup your authorization system so that it always authenticates (and authorizes) unless you explicitly opt out. This is a best practice that reduces the possible of security breaches simply due to programmer omission. You opt out by calling skip_before_action :authorize_user.
Instead of your set_current_user use a memoized getter method (current_user) to remove issues caused by the ordering of callbacks. ||= is conditional assignment and will prevent it from querying the database again if you have already fetched the user. This should be the ONLY method in the system that knows how the user is stored. Do not access #current_user directly to avoid leaking the implementation details into the rest of the application.
Methods ending with ? are by convention predicate methods in Ruby and should be expected to return a boolean. Name your modules by what their responsibility is and not what code they contain - avoid the postfix Concern as it tells you nothing about what it does.
I have my Banks Controller
class Api::V1::BanksController < ApplicationController
before_action :authenticate_user!
respond_to :json
# PUT /api/v1/banks/:id.json
def update
#bank = UserBank.find_by!(uuid: params[:id])
if #bank.update_attributes bank_params
render json: #bank
else
render json: #bank.errors, status: :unprocessable_entity
end
end
private
def bank_params
params.require(:bank).permit(:iban, :bic)
end
end
I'm using devise for the authentication. My problem comes from the fact that any users can update another user's bank object just by getting the access-token from the login response.
Is there a clean/secure/automatic way of preventing a user to interact with somebody else's details ?
or should I just make sure that the bank object I'm updating belongs to the logged-in user ?
thanks a lot
Your mixing up authentication and authorization.
Authentication is concerned with the identity of the user.
Authorization is a set of rules for who is allowed to do what in your application.
Devise provides authentication, you can either create your own authorization system or use a library (recommended) such as Pundit or CanCanCan.
A hacky home rolled authorization check would look like:
class AuthorizationError < StandardError; end
class ApplicationController
rescue_from AuthorizationError, with: :deny_access
def deny_access
head :unauthorized and return
end
end
class Api::V1::BanksController < ApplicationController
before_action :authenticate_user!
before_action :set_bank!
before_action :authorize!
respond_to :json
# PUT /api/v1/banks/:id.json
def update
#bank = UserBank.find_by!(uuid: params[:id])
respond_with #bank.update(bank_params)
end
private
def set_bank!
#bank = UserBank.find_by!(uuid: params[:id])
end
def authorize!
# this is the authorization rule.
unless #bank.user == current_user
raise AuthorizationError
end
end
def bank_params
params.require(:bank).permit(:iban, :bic)
end
end
I am rolling R Bates authentication from scratch from here, and I'm wanting to put the call to the authorize method in the application controller. Basically I want the entire app locked down. Here is the app controller...
class ApplicationController < ActionController::Base
before_filter :authorize
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def authorize
redirect_to login_url, alert: "Not authorized" if current_user.nil?
end
end
But the probably is I'm getting an infinite loop in my URL call. How should I hand this?
sessions controller
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
flash.now.alert = "Email or password is invalid"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Logged out!"
end
end
You can skip before_filter (like in #vee answer) for some actions of SessionsController:
skip_before_filter :authorize, only: [:new, :create]
Alternatively you can modify authorize method to avoid redirects in some cases:
def authorize
return if skip_authorization?("#{controller_name}##{action_name}")
redirect_to login_url, alert: "Not authorized" if current_user.nil?
end
def skip_authorization?(location)
%w(sessions#new sessions#create).include?(location)
end
The loop is because of the redirect_to login_url....
You should skip the authorize filter in controller that has the login action defined as:
class SessionsController < ApplicationController
skip_before_filter :authorize, only: :login
def login
...
end
...
end
Or to skip authorize filter for all actions, use skip_before_filter :authenticate without the only option.
I'm using devise and trying the next following:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :is_worker
def is_worker
if user_signed_in?
#email = current_user.email
if #email && Worker.find_by_email(#email).nil?
redirect_to '/tasksadmins'
else
redirect_to '/workers'
end
else
redirect_to '/users/sign_in'
end
end
end
when I try to enter the site: localhost:3000/tasksadmins, I got:
Oops! It was not possible to show this website
The website at http://localhost:3000/tasksadmins seems to be unavailable. The precise error was:
Too many redirects
It could be temporarily switched off or moved to a new address. Don't forget to check that your internet connection is working correctly.
How can I fix it please?
before_filter is applied to every single request. That's why it's redirecting again and again.
You might want to only filter specific actions:
before_filter :is_worker, only: :index
Another solution would be to check wether a redirect is necessary in #is_worker:
redirect_to '/workers' unless request.fullpath == '/workers'
EDIT:
Another way would be to skip the before filter for the target actions of your redirects. Example:
class WorkersController < ApplicationController
skip_before_filter :is_worker, only: :index
# …
end
In my case:
users_controller.rb
before_action :logged_in?, only: :new
def new
#user = User.new
render layout: "session"
end
and
application_controller.rb
def logged_in?
redirect_to users_new_url unless current_user.present?
end
When I was trying to redirect to the 'users/new' page,same error occurred.
This is just because I'm trying to redirect to the 'users/new' page and "def logged_in?" is also redirecting to the same page.
Then I changed the application_controller.rb code like this:
def logged_in?
redirect_to root_url unless current_user.blank?
end
Error_Resolved.
In my application, I store the user's ID in session[]. At the beginning of every controller action, I'm calling a method defined in the ApplicationController called current_user:
def current_user
#current_user ||= session[:current_user_id] &&
User.find_by_id(session[:current_user_id])
end
At the beginning of my controllers' methods, I have the following:
#current_user = current_user
if #current_user == nil
redirect_to :home
return
end
This is obviously repetitive code and should be a method somewhere. I read the answer for this question, and tried putting my method into a parent class that my controller classes now descend from, however it seems like I can't redirect from that method now.
In my parent class, I have:
def verify_user
user = current_user
if user == nil
redirect_to "/"
return
end
return user
end
And now I've changed my controller methods to this:
#current_user = verify_user
This doesn't work, and I think I know why. For one, I can't simply call return in my verify_user method, as that obviously will just return to the controller. The redirect doesn't seem to have any affect, probably because format.html is being called after the redirect call, which was the reason for the return in the original code.
So, what am I doing wrong here, and what suggestion do you have to solve it? Is this the wrong approach? My main goal is to keep the entire "check if user is logged in otherwise redirect" to one line of code per controller method.
Take a look at the devise gem https://github.com/plataformatec/devise. It handles a lot of this basic user authentication logic for you. This specific problem can we solved by adding before_filter :authenticate_user! to the controllers or actions that need to be guarded.
Add the following logic to the ApplicationController class:
class ApplicationController < ActionController::Base
def current_user
...
end
def logged_in?
current_user.present?
end
def require_user
return true if logged_in?
render_error_message("You must be logged in to access this page",
new_user_session_url)
return false
end
def render_message message
respond_to do |format|
format.html {
if request.xhr?
render(:text => message, :status => :unprocessable_entity)
else
redirect_to(root_url, :notice => message)
end
}
format.json { render :json => message, :status => :unprocessable_entity }
format.xml { render :xml => message, :status => :unprocessable_entity }
end
end
end
Now add a before_filter to your controller:
class OrdersController < ApplicationController
before_filter :require_user
end