user3266894
user3266894

Reputation: 41

Ruby on Rails 4: authorisation after authentication with multiple Devise models

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

Answers (2)

Eric Wanchic
Eric Wanchic

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.

  • First, Does user exist? - authentication
  • Second, Does user have login? - authorization
  • Third, What are the permissions? - authorization

Upvotes: 0

miahabdu
miahabdu

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

Related Questions