Devise limit one session per user at a time

My app is using Rails 3.0.4 and Devise 1.1.7.

I'm looking for a way to prevent users from sharing accounts as the app is a subscription based service. I've been searching for over a week, and I still don't know how to implement a solution. I'm hoping someone has implemented a solution and can point me in the right direction.

Solution (Thank you everyone for your answers and insight!)

In application controller.rb

before_filter :check_concurrent_session

def check_concurrent_session
  if is_already_logged_in?
    sign_out_and_redirect(current_user)
  end
end

def is_already_logged_in?
  current_user && !(session[:token] == current_user.login_token)
end

In session_controller that overrides Devise Sessions controller:

skip_before_filter :check_concurrent_session

def create
  super
  set_login_token
end

private
def set_login_token
  token = Devise.friendly_token
  session[:token] = token
  current_user.login_token = token
  current_user.save
end

In migration AddLoginTokenToUsers

def self.up
  change_table "users" do |t|
    t.string "login_token"
  end
end

def self.down
  change_table "users" do |t|
    t.remove "login_token"
  end
end

This gem works well: https://github.com/devise-security/devise-security

Add to Gemfile

gem 'devise-security'

after bundle install

rails generate devise_security:install

Then run

rails g migration AddSessionLimitableToUsers unique_session_id

Edit the migration file

class AddSessionLimitableToUsers < ActiveRecord::Migration
  def change
    add_column :users, :unique_session_id, :string, limit: 20
  end
end

Then run

rake db:migrate

Edit your app/models/user.rb file

class User < ActiveRecord::Base
  devise :session_limitable # other devise options
  ... rest of file ...
end

Done. Now logging in from another browser will kill any previous sessions. The gem actual notifies the user that he is about to kill a current session before logging in.


You can't do it.

  • You can control IP addresses of user, so you can prevent presence of user from two IP at a time. ANd you can bind login and IP. You can try to check cities and other geolocation data through IP to block user.
  • You can set cookies to control something else.

But none of this will guarantee that only one user uses this login, and that those 105 IP from all over the world doesn't belong to only one unique user, which uses Proxy or whatever.

And the last: you never need this in the Internet.

UPD

However, what I'm asking is about limiting multiple users from using the same account simultaneously which I feel should be possible

So you can store some token, that will contain some encrypted data: IP + secret string + user agent + user browser version + user OS + any other personal info: encrypt(IP + "some secret string" + request.user_agent + ...). And then you can set a session or cookie with that token. And with each request you can fetch it: if user is the same? Is he using the same browser and the same browser version from the same OS etc.

Also you can use dynamic tokens: you change token each request, so only one user could use system per session, because each request token will be changed, another user will be logged out as far as his token will be expired.


This is how I solved the duplicate session problem.

routes.rb

  devise_for :users, :controllers => { :sessions => "my_sessions" }

my_sessions controller

class MySessionsController < Devise::SessionsController
  skip_before_filter :check_concurrent_session

  def create
    super
    set_login_token
  end

  private
  def set_login_token
    token = Devise.friendly_token
    session[:token] = token
    current_user.login_token = token
    current_user.save(validate: false)
  end
end

application_controller

  def check_concurrent_session
    if duplicate_session?
      sign_out_and_redirect(current_user)
      flash[:notice] = "Duplicate Login Detected"
    end
  end

  def duplicate_session?
    user_signed_in? && (current_user.login_token != session[:token])
  end

User model Add a string field via a migration named login_token

This overrides the default Devise Session controller but inherits from it as well. On a new session a login session token is created and stored in login_token on the User model. In the application controller we call check_concurrent_session which signs out and redirects the current_user after calling the duplicate_session? function.

It's not the cleanest way to go about it, but it definitely works.