37

I have a lot of users thanks to devise and I want to ban a few problem makers. Does Devise have this support built in?

Thanks

AnApprentice
  • 108,152
  • 195
  • 629
  • 1,012
  • 1
    Possible duplicate of [What is the best way to ban/block users with Devise for Rails?](https://stackoverflow.com/questions/3894919/what-is-the-best-way-to-ban-block-users-with-devise-for-rails) – Wit Jun 19 '17 at 14:32
  • see also [How-to:-Soft-delete-a-user-when-user-deletes-account](https://github.com/plataformatec/devise/wiki/How-to:-Soft-delete-a-user-when-user-deletes-account) – mb21 Jan 12 '18 at 09:07

4 Answers4

88

From the devise doku for authenticatable.rb:

Before authenticating a user and in each request, Devise checks if your model is active by calling model.active_for_authentication?. This method is overwriten by other devise modules. For instance, :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.

You overwrite this method yourself, but if you do, don't forget to call super:

def active_for_authentication?
  super && special_condition_is_valid?
end

So, when you have a flag blocked in the user database, the method in the user model looks something like this:

def active_for_authentication?
  super && !self.blocked
end
Community
  • 1
  • 1
Frank
  • 881
  • 1
  • 6
  • 2
  • 3
    Yep, I think this method is the best one to have a "blocked" strategy, thanks Frank ! – Kulgar Jun 03 '12 at 18:33
  • 3
    To customize the error message shown to the user with this approach, edit `config/locales/devise.en.yml` and edit the value under en > devise > failure > inactive – tirdadc Mar 31 '15 at 14:38
  • 2
    If you don't want to use the `inactive` failure message, you can add a custom one by also overriding `inactive_message` also as seen [here](https://github.com/plataformatec/devise/blob/master/lib/devise/models/authenticatable.rb#L50). – brendanwb Jun 07 '16 at 16:27
  • 1
    This one works great! You override the active_for_authentication? method on your user.rb and It will block the user at his next request, showing your login page and the error message. Just as I wanted and better than the selected answer. Thanks! – Mauricio Moraes Aug 23 '16 at 19:02
  • This might still allow blocked users to login after resetting the password. – Tyler Amos Jun 26 '20 at 20:23
33

I just implemented this in my project myself. What I did was similar to Kleber above, I defined this in my app/controllers/sessions_controller.rb (overriding Devise)...

class SessionsController < Devise::SessionsController

protected

  def after_sign_in_path_for(resource)
    if resource.is_a?(User) && resource.banned?
      sign_out resource
      flash[:error] = "This account has been suspended for violation of...."
      root_path
    else
      super
    end
   end

end

And then I added a boolean column to Users called 'banned,' so the moderators check the checkbox when editing the user in the backend, and the boolean will return true.

But there was one flaw...if a user was already logged in and then banned, they still had access to doing stuff on the site (comments, etc) at least until their session expired or they logged out. So I did this in the app/controllers/application_controller.rb...

class ApplicationController < ActionController::Base
  before_filter :banned?

  def banned?
    if current_user.present? && current_user.banned?
      sign_out current_user
      flash[:error] = "This account has been suspended...."
      root_path
    end
  end
end

That'll automatically log them out if a ban is detected. Anyway, not sure this whole thing is the "best" way to factor the whole thing as I'm newer to Rails, but the whole thing works for me and hope it will at least give you a good start.

Shannon
  • 2,744
  • 3
  • 28
  • 37
  • 2
    +1 worked great! I don't think overriding after_sign_in_path_for is necessary though. Implementing the before_filter in the app controller is all thats needed but after_sign_in_path_for may be useful to some. Thanks. – Tim Santeford Jun 10 '11 at 14:05
  • Good observation, didn't think about that, nice refactoring idea and I might implement that. However if you're in a situation where you don't need to check a banned user all the time and only on login, I would choose the Devise login override. But several options here to cover different situations. – Shannon Jun 13 '11 at 03:59
  • This did not work for me in Rails 3, but @Frank 's answer did – Dinedal Apr 20 '12 at 01:36
2

Customize user account status validation when logging in

Sometimes you want to add custom validation to the user before logging them in. In this case I needed to implement an account_active boolean (true or false) check. So if it's true it will allow the user to login and create a session, if false it will display the "account not active" error.

Overwrite the active_for_authentication? method in your model (User) and add your validation logic. You want to return super && (true or false)

  def active_for_authentication?
    # Uncomment the below debug statement to view the properties of the returned self model values.
    # logger.debug self.to_yaml

    super && account_active?
  end

In this case it checks the boolean value to account_active and it return it's value to the sign in method that called it.

The original active_for_authentication? method can be found in devise/lib/devise/models/authenticatable.rb.

Note: active_for_authentication? is called by Devise after authenticating user and in each request that follows. This means whatever code you add to the override of active_for_authentication? will be run for every request during the user's session.

Note: active_for_authentication? is not being called for each request when using database_authenticatable. It only gets called on login. Not sure if this is intended behavior.

Customize error message

If the method 'active_for_authentication?' returns false, method 'inactive_message' is invoked, user will receive notification for being inactive. We need to customize the message as well:

  def inactive_message
    account_active? ? super : :account_inactive
  end

Now this will refer the custom message for 'account_inactive' and not 'inactive', which we need to define in devise translation file.

 devise:
     failure:
       inactive: 'Your account was not activated yet.'
       account_inactive: 'Your account is not active.'
     registrations:
       signed_up_but_account_inactive: "Thanks for signing up. We'll let you know when your account is active"
Marcelo Austria
  • 861
  • 8
  • 16
-4

You could add a field called "banned" to your Users table.

and then, on your Controller, you can have something like this:

class UsersController < ApplicationController
before_filter :deny_banned

protected
def deny_banned
  if current_user.banned?
    redirect_to root_path, :notice => "You are banned from this site."
  end
end

end

This is not complete but I hope it helps you somehow.

Kleber S.
  • 8,110
  • 6
  • 43
  • 69