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:
- Each user has a unique token stored in the database.
- This token is also stored in the browser session when logging in.
- On each request, we validate that both tokens match.
- 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.