Skip to main content
โšก Calmops

Sessions in Rails: How They Work and Best Practices

Introduction

HTTP is a stateless protocol โ€” each request is independent and the server has no memory of previous requests. Sessions solve this by maintaining state across requests, enabling features like user authentication, shopping carts, and multi-step forms.

Rails provides a robust session system built on top of cookies, with multiple storage backends and strong security defaults.

How Sessions Work

The basic flow:

  1. User submits login credentials
  2. Server validates credentials
  3. Server creates a session and stores user data (user ID, role, etc.)
  4. Server sends a session identifier to the browser as a cookie
  5. Browser includes the cookie in every subsequent request
  6. Server reads the cookie, looks up the session, and knows who the user is
Browser                          Rails Server
  |                                   |
  |  POST /login {email, password}    |
  |---------------------------------->|
  |                                   |  validate credentials
  |                                   |  create session
  |  200 OK                           |
  |  Set-Cookie: _session_id=abc123   |
  |<----------------------------------|
  |                                   |
  |  GET /dashboard                   |
  |  Cookie: _session_id=abc123       |
  |---------------------------------->|
  |                                   |  look up session
  |  200 OK + dashboard content       |  user_id = 42
  |<----------------------------------|

Rails Session Basics

Rails provides a session hash that persists across requests:

# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def create
    user = User.find_by(email: params[:email])

    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      session[:user_role] = user.role
      redirect_to dashboard_path, notice: "Logged in successfully"
    else
      flash.now[:alert] = "Invalid email or password"
      render :new, status: :unprocessable_entity
    end
  end

  def destroy
    session.delete(:user_id)
    session.delete(:user_role)
    # Or clear everything:
    reset_session
    redirect_to root_path, notice: "Logged out"
  end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  helper_method :current_user, :logged_in?

  private

  def current_user
    @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
  end

  def logged_in?
    current_user.present?
  end

  def require_login
    unless logged_in?
      redirect_to login_path, alert: "Please log in to continue"
    end
  end
end

Session Storage Options

Rails supports several session storage backends, each with different trade-offs:

The entire session data is stored in an encrypted, signed cookie on the client:

# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
  key: '_myapp_session',
  expire_after: 2.weeks,
  secure: Rails.env.production?,  # HTTPS only in production
  httponly: true,                  # not accessible via JavaScript
  same_site: :lax

Pros:

  • No server-side storage needed
  • Scales horizontally without shared state
  • Fast โ€” no database lookup per request

Cons:

  • Limited to ~4KB
  • Session data is visible to the client (though encrypted)
  • Cannot invalidate individual sessions server-side

2. Cache Store

Sessions stored in Rails cache (Redis, Memcached):

Rails.application.config.session_store :cache_store,
  key: '_myapp_session',
  expire_after: 2.hours

Pros:

  • Fast reads/writes
  • Can store larger sessions
  • Can invalidate sessions server-side

Cons:

  • Sessions lost if cache is cleared
  • Requires cache infrastructure

3. Active Record Store

Sessions stored in the database:

rails generate active_record:session_migration
rails db:migrate
Rails.application.config.session_store :active_record_store,
  key: '_myapp_session'

Pros:

  • Persistent โ€” survives server restarts
  • Can query and manage sessions
  • Can invalidate individual sessions

Cons:

  • Slower โ€” database query on every request
  • Requires database cleanup for expired sessions
gem 'redis-session-store'
Rails.application.config.session_store :redis_session_store,
  key: '_myapp_session',
  redis: {
    expire_after: 2.hours,
    key_prefix: 'myapp:session:',
    url: ENV['REDIS_URL']
  }

Pros:

  • Fast, scalable, persistent
  • Can invalidate sessions
  • Works well in multi-server deployments

Security Best Practices

Always Use reset_session After Login

Regenerating the session ID after login prevents session fixation attacks:

def create
  user = User.find_by(email: params[:email])

  if user&.authenticate(params[:password])
    reset_session  # generate new session ID
    session[:user_id] = user.id
    redirect_to dashboard_path
  end
end

Store Minimal Data in Sessions

Only store what you need โ€” typically just the user ID:

# Good: store only the ID
session[:user_id] = user.id

# Bad: storing sensitive or large data
session[:user] = user.to_json  # don't do this
session[:credit_card] = card_number  # never do this

Set Appropriate Expiry

# Short-lived sessions for sensitive apps
Rails.application.config.session_store :cookie_store,
  expire_after: 30.minutes

# Longer sessions with "remember me" functionality
def create
  if params[:remember_me]
    cookies.permanent[:user_token] = user.generate_remember_token
  else
    session[:user_id] = user.id
  end
end

Protect Against CSRF

Rails includes CSRF protection by default. Make sure it’s enabled:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception  # default in Rails
end

Secure and HttpOnly Cookies

Rails.application.config.session_store :cookie_store,
  key: '_myapp_session',
  secure: true,    # only sent over HTTPS
  httponly: true   # not accessible via document.cookie

Implementing “Remember Me”

# User model
class User < ApplicationRecord
  def generate_remember_token
    token = SecureRandom.urlsafe_base64
    update_column(:remember_token, BCrypt::Password.create(token))
    token
  end

  def authenticated_by_token?(token)
    BCrypt::Password.new(remember_token).is_password?(token)
  end

  def forget
    update_column(:remember_token, nil)
  end
end

# SessionsController
def create
  user = User.find_by(email: params[:email])

  if user&.authenticate(params[:password])
    reset_session
    session[:user_id] = user.id

    if params[:remember_me] == '1'
      token = user.generate_remember_token
      cookies.permanent.signed[:remember_token] = {
        value: "#{user.id}:#{token}",
        httponly: true,
        secure: Rails.env.production?
      }
    end

    redirect_to dashboard_path
  end
end

# ApplicationController
def current_user
  if session[:user_id]
    @current_user ||= User.find_by(id: session[:user_id])
  elsif cookies.signed[:remember_token]
    user_id, token = cookies.signed[:remember_token].split(':')
    user = User.find_by(id: user_id)
    if user&.authenticated_by_token?(token)
      session[:user_id] = user.id
      @current_user = user
    end
  end
end

Debugging Sessions

# In a controller or view (development only)
Rails.logger.debug "Session: #{session.to_hash}"

# In rails console
# Check session data for a specific session ID
ActiveRecord::SessionStore::Session.find_by(session_id: 'abc123')

Summary

Storage Speed Persistence Scalability Invalidation
Cookie store Fast Client-side Excellent No
Cache store Fast Volatile Good Yes
Database store Slow Persistent Poor Yes
Redis store Fast Persistent Excellent Yes

For most production Rails apps, Redis is the recommended session store โ€” it’s fast, persistent, and supports session invalidation.

Resources

Comments