Reputation: 41
I created a simple Ruby on Rails 4 app with a few simple models and a PosgreSQL database that is deployed on my test VPS. Then I created three Devise models using Rails generators.
I chose separate Devise models since the models are completely different from each other. The single table inheritance method was out of question.
I understand that with any Devise model I can perform authentication on site, register 'Devised' users, etc. That stuff works.
Now I plan to set up a place to make my authorisation things. I dropped CanCan as it is not supported by Rails 4 at the moment according to what I have found using Google.
So the most suitable option I found was simply using a before_filter
and a custom authentication method, which in turn checks for current_user
type or its existence and returns if it is good to go or not.
Here is the sample pseudo code I tried already and it looks like it works.
before_filter :custom_authentication!
def custom_authentication!
if current_admin
redirect_to "admin_page"
elsif current_monkey
redirect_to "zoo"
elsif current_human
redirect_to "home"
else
# some unauthorised access alert
end
end
As I understand it, I need to put this kind of code into each controller I have in my Rails 4 app. Is that correct?
My main question is this: is there some better way to do this? I was thinking of putting a single method in application_controller.rb
that builds the whole authorisation logic for each type of Devise-enabled user model that ever accesses my app controllers. Would that be a better place for this logic? Is this the correct approach? Or do I need to restart and use a different approach?
Upvotes: 4
Views: 1769
Reputation: 2074
Like miahabdu said, first authentication (see if user exists), then authorization (see user's permissions).
However, you don't have to stop using devise, nor do you need Cancan.
Here is a simple way to do the 'Authorization' right after authentication.
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 :authenticate_user!
before_action :check_access
private
def check_access
if current_user.present?
unless find_permission(current_user, 'code assigned for login access')
sign_out current_user
redirect_to new_user_session_path
end
end
end
end
Don't get too hung up on what find_permission
is. Basically current_user
is from devise, and sign_out current_user
does exactly that. But, right after you sign the current_user
out, you need to redirect_to new_user_session_path
, assuming the User
Model is what you are using for devise.
Now, the only minor change I would have done in your code is to use a CaseElse instead of IfElse.
So, to implement your code into my example, I would do this to the check_access
method:
def check_access
if current_user.present?
unless find_permission(current_user, 'code assigned for login access')
sign_out current_user
redirect_to new_user_session_path
end
if current_admin
redirect_to "admin_page"
elsif current_monkey
redirect_to "zoo"
elsif current_human
redirect_to "home"
else
sign_out current_user
redirect_to new_user_session_path
end
end
end
Finally, why check logout access at the beginning? It's quicker. You won't have to spend system time checking all the permissions when in the end, the user isn't permissioned to log in anyway.
Upvotes: 0
Reputation: 614
I've run into the same problem trying to find a user authorization solution after finding CanCan not supported in Rails 4. The option that I chose to go with is Pundit. It's really flexible and works really well.
https://github.com/elabs/pundit
Basically, you set up a bunch of policy classes that manages user authorization for your controller actions.
A posts_controller would have a policy class similar to this:
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
@user = user
@post = post
end
def update?
user.admin?
end
end
and you would authorize the update action in the controller:
def update
@post = Post.find(params[:id])
authorize @post
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
So if the user was not an admin, it would throw an error. Pundit allows you to rescue the denied authorization in the ApplicationController and allows you to add your custom code. So for example you could do something like this:
class ApplicationController < ActionController::Base
protect_from_forgery
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
if current_monkey
redirect_to "zoo"
elsif current_human
redirect_to "home"
else
flash[:error] = "You are not authorized to perform this action."
redirect_to request.headers["Referer"] || root_path
end
end
end
You can also configure policies for multiple devise-users in the same Policy class. For example, in the PostPolicy class, you can check the user's type and grant privileges based on that:
def index?
return true if user.type == "Monkey"
end
Upvotes: 2