Allow unconfirmed users to access certain pages which require authentication
Asked Answered
I

2

11

I use the Rails Stack with

  • devise
  • warden
  • confirmable

Now I have a certain requirement related to email confirmation and access provision to unverified users. Let's say there are 3 categories of pages:

  1. case 1 - requires no authentication.
  2. case 2 - requires authentication and also require the user to be confirmed.
  3. case 3 - requires authentication (correct username and password combination) but user need not be confirmed to access them.

With devise and confirmable, implementing case 1 and case 2 is a breeze. Once the user does login/signup, I redirect to "confirm your email page".

My problem is with case 3. Suppose the user does his login/signup. He is yet to confirm his email address but should be allowed to visit certain case 3 routes. When he visits a case 3 routes, I expect:

  • no redirection
  • valid session

Devise with confirmable either allows all the pages to be visited by confirmed users or none. It does not allow access to certain pages with authentication but without confirmation.

I tried overriding the devise confirmed? by implementing this logic:

class ApplicationController < ActionController::Base
   before_filter :verify_user
   def verify_user
       $flag = true if CERTAIN_ROUTES.include?(action_class)
   end
end

class User < ActiveRecord::Base
   def confirmed?
      $flag || !!confirmed_at
   end
end

This barely works for sign in but not for sign up. Also, this is an extremely bad way to achieve it. How should I approach this problem? Other functionalities work fine.

Invar answered 7/8, 2018 at 10:31 Comment(0)
A
10

Instead of overwriting confirmed? you could just overwrite the confirmation_required? model method (docs):

# user.rb
class User < ActiveRecord::Base
  protected

  def confirmation_required?
    false
  end
end

After that you can handle the confirmation logic yourself, by redirecting all unconfirmed users in a before_action when the controllers require confirmation, or you can pull this into your authorization logic (e.g. with pundit).

class ApplicationController < ActionController::Base
   def needs_confirmation
     redirect_to root_path unless current_user.confirmed?
   end
end

class SomeController < ApplicationController
  before_action :needs_confirmation
end
Accusatory answered 9/8, 2018 at 12:26 Comment(4)
Thanks a lot for this answer. But here is the issue. I may have 50 routes, among which I need 4 routes to be visited with authentication but without confirmation. I would have to write before_action :needs_confirmation in every controller and make sure any other dev who writes a new controller adds this filter. Can you suggest any other neat solution?Invar
then just put the ´before_action´ in the ApplicationController and use skip_before_action :needs_confirmation in the controller where you want to have unconfirmed access. apidock.com/rails/v4.2.7/AbstractController/Callbacks/…Accusatory
This looks like a better solution. Let me try it out and come back to you on this. Will need 24 hours.Invar
Thanks a ton. This solution works out for me quite well.Invar
M
3

You should take a look at the gem 'pundit' - it works well with devise.

https://github.com/varvet/pundit

Rather than writing controller before_actions etc, you write policies which will cover each of your authorization requirements, and then use those policies inside your controllers.

For example, in a controller:

class ExampleController < ApplicationController
    before_action { authorize :example }

    def case_one
       # action
    end

    def case_two
       # action
    end

    def case_three
       # action
    end
end

Then your policy would be kept under app/policies/example_policy.rb

class ExamplePolicy < ApplicationPolicy
  attr_reader :user

  def initialize(user, _)
    @user = user
  end

  def case_one?
    true
  end

  def case_two?
    user.present? && user.confirmed_at.present?
  end

  def case_three?
    user.present?
  end
end

It works really well, especially in other cases where you are determining authorization against a type of resource.

Mediative answered 10/8, 2018 at 10:1 Comment(4)
How would I redirect the user to a confirmation page (where they can request a new confirmation email) if confirmed_at is not present?Florafloral
You can rescue the Pundit::NotAuthorizedError and then redirect the user to your confirmation page. See github.com/varvet/… for detailsMediative
Ok, but what if I have 50 controllers and I want to give access to only 3 of them to users who do not meet my authorization requirements? I would have to place authorizations in each of the 47 controllers and in each newly created one (assuming that the number of controllers available to all doesn't changed). Am I right or I missed something?Beverleebeverley
mr_muscle - no, you wouldn't do that. You would either change your ApplicationController to implement the authorization before_action and then skip that before action in controllers where you don't want it ran. This is just one way of doing it, depending on your app you may want to create an AuthenticatedApplicationController to inherit from etc.Mediative

© 2022 - 2024 — McMap. All rights reserved.