In enterprise web applications, it’s common to face the need to invalidate a specific user’s sessions immediately. This can happen in situations such as:

  • Employees leaving the company
  • Suspected credential compromise
  • Role changes that require re-authentication
  • Corporate security policies

The technical challenge arises when we need to invalidate all active sessions of a user without affecting other users in the system, and do it efficiently and reliably.

Session Tokens per user

The solution is based on implementing a unique “session token” per user. The mechanism works as follows:

  1. Each user has a unique token stored in the database.
  2. This token is also stored in the browser session when logging in.
  3. On each request, we validate that both tokens match.
  4. To invalidate sessions, we regenerate the user’s token in the database.

When the user’s sessions attempt to make new requests, the validation will automatically fail, forcing a new authentication.

Implementation

The first step consists of adding a session_token column to the users table and assigning each one a token:

class AddSessionTokenToUsers < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :session_token, :string

    # Generate tokens for existing users
    User.find_each do |user|
      user.update_column(:session_token, SecureRandom.hex(16))
    end

    change_column_null :users, :session_token, false
  end
end

Then, we need to add session token generation in the model:

class User < ApplicationRecord
  before_create :generate_session_token
  before_update :generate_session_token, if: :password_changed?

  def invalidate_sessions!
    generate_session_token
    save!
  end

  private

  def generate_session_token
    self.session_token = SecureRandom.hex(16)
  end
end

Important points:

  • The token is automatically generated when creating the user
  • The token is regenerated when the password changes
  • We add a public invalidate_sessions! method for manual invalidation

Finally, we need to add session token validation on each request:

class ApplicationController < ActionController::Base
  before_action :validate_session_token, if: :user_signed_in?

  private

  def validate_session_token
    return if session[:session_token] == current_user.session_token

    sign_out current_user
    session.delete(:session_token)
    redirect_to login_path, alert: 'Your session has expired. Please log in again.'
  end
end

And save the session token in the browser session when logging in:

class SessionsController < ApplicationController
  skip_before_action :validate_session_token
  
  def create
    user = User.find_by(email: params[:email])

    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      session[:session_token] = user.session_token
      redirect_to root_path, notice: 'Successful login'
    else
      render :new, alert: 'Invalid credentials'
    end
  end
end

Conclusion

This session tokens per user implementation is one of those solutions that seems simple but elegantly solves a complex problem. With just a few extra lines of code, you have total control over your users’ sessions without impacting performance or the experience of the rest.

Did you know this strategy? Have you implemented any other solution for this problem? Leave me a comment telling me about your experience, and if this article was helpful, don’t forget to give it some claps.