← Back to blog
How Sessions work in Rails
· 8 min read · rails sessions security

How Sessions work in Rails

Sessions are a fundamental part of most web applications. They allow web applications to remember information about a user across multiple requests. Without sessions, every HTTP request would be completely independent, making features like login systems impossible.

The Problem: HTTP is Stateless

HTTP is a stateless protocol. This means that every request sent by a browser to a server is independent. The server does not automatically remember anything about previous requests.

For example:

  • A user logs into a website
  • The server verifies the credentials
  • The user navigates to another page

Without sessions, the server would have no memory of whether the user has already logged in.

This is where sessions come in.

What is a Session?

A session is a mechanism that persists data across multiple requests for the same user, using a server-side store (or a client-side signed cookie) identified by a cookie sent with each request.

In Rails, session data is typically stored as a hash-like object that can be accessed inside controllers and views. Behind the scenes, a lot of crypto and HTTP plumbing make it work safely.

πŸ‘‰ By default, Rails uses CookieStore (client-side), but other stores use a session ID and server-side storage.

Example:

Store session in a request

reset_session
session[:user_id] = user.id

Read it in another request:

current_user = User.find(session[:user_id])

How Sessions Work in Rails

Let's take an example of authentication in your app

1. User visit page first time

A user visits your app for the first time.

GET /dashboard

At this point:

  • There is no cookie present in the request
  • Rails initializes a new empty session Even though the session is empty, Rails still sends a cookie in the response:
Set-Cookie: _app_session=<encrypted_empty_session>

What does this cookie contain?

πŸ‘‰ An encrypted + signed serialized session hash, generated using secret key base

This ensures that on the next request, the browser can send the session back.

2. User Logs In (Session is Updated)

When the user logs in successfully:

session[:user_id] = user.id # user is an object with an id attribute

Rails now:

  1. Updates the session hash with user_id: user.id
  2. Serializes it
  3. Encrypts it
  4. Signs it (HMAC)
  5. Sends a new cookie
Set-Cookie: _app_session=<encrypted_user_session>

πŸ‘‰ The previous anonymous session cookie is replaced with a new one containing user_id.

By default, Rails uses ActionDispatch::Session::CookieStore So the entire session is stored inside the cookie.

3. Subsequent Requests (Session is Read)

For every request after login:

GET /dashboard
Cookie: _app_session=xyz...

Rails does the following:

  1. Reads the cookie
  2. Splits payload and signature
  3. Decrypts the data
  4. Verifies the signature using HMAC and secretkeybase
  5. Deserializes it into a Ruby hash

What if the cookie is tampered with?

  • HMAC verification fails ❌
  • Rails resets session to {}
  • No crash, fails safely

πŸ‘‰ If session is not modified, Rails does NOT send a new cookie.

How Sessions Work Internally (Middleware Overview)

Now that we understand how sessions behave from the outside, let’s briefly see how Rails handles this internally.

Rails uses a Rack middleware stack, where each layer is responsible for a small part of the request.

Request Flow

  • Browser Request
  • ↓
  • ActionDispatch::Cookies
  • ↓
  • ActionDispatch::Session::( can be CookieStore, CacheStore, ActiveRecordStore, or a custom store.)
  • ↓
  • Rails Controller

What Each Middleware Does

1. ActionDispatch::Cookies

  • Reads the raw Cookie header from the browser
  • Parses it into a Ruby hash
  • Makes cookies available via:
request.cookies

πŸ‘‰ At this point, cookies are just strings (not decrypted yet).

2. ActionDispatch::Session::CookieStore

This is where the actual session logic happens.

  • Extracts _app_session from cookies
  • Decrypts the data
  • Verifies integrity using HMAC
  • Deserializes it into a Ruby hash
env['rack.session']

3. Rails Controller

Inside your controller session[:user_id] this is just a wrapper over env['rack.session']

Response Flow

After the controller finishes:

  • If session was modified:
    • Rails serializes + encrypts + signs it
    • Sends Set-Cookie header
  • If session was NOT modified:
    • No cookie is sent (performance optimization)

Session Storage Options in Rails

Rails supports different session storage mechanisms. You can update session store mechanism in config/initializers/session_store.rb

# config/initializers/session_store.rb
config.session_store :cookie_store,
  key: '_myapp_session',
  secure: Rails.env.production?,
  same_site: :lax,
  expire_after: 2.weeks

1. Cookie Store (Default)

By default, Rails uses CookieStore. The entire session data is serialized, AES-256-GCM, and stored in the client's cookie β€” signed with an HMAC using your secretkeybase.

Pros

  • Zero server memory or DB usage
  • Works instantly β€” no extra infrastructure needed
  • Scales to any number of servers automatically
  • Tamper-proof via HMAC signature

Cons

  • Hard 4 KB size limit per cookie
  • Cannot invalidate a session server-side
  • Rotating secret_key_base logs every user out immediately

When to use

Default choice for most apps. Store only a user's id β€” never sensitive data. Avoid if you need server-side invalidation (e.g. "log out all devices") or if you store large objects in the session.

2. Cache Store

# config/initializers/session_store.rb

config.session_store :cache_store

Sessions are stored in whatever Rails.cache is configured to use β€” typically Redis or Memcached. The browser cookie holds only a session ID (not the data). Data lives on the server.

Pros

  • No 4 KB cookie limit β€” but large session data is still discouraged
  • Server-side invalidation (instant logout)
  • Fast β€” Redis lookup is sub-millisecond
  • Shares infrastructure with your existing app cache

Cons

  • Sessions lost if cache is cleared or evicted
  • Requires Redis or Memcached running
  • Extra network hop on every request
  • Cache eviction can silently log users out

When to use

Best choice for production apps already using Redis (e.g. for Sidekiq or Action Cable). Great when you need "log out everywhere" or store more than a few bytes in the session.

3. ActiveRecord Store

Requires the activerecord-session_store gem and a migration. Sessions are rows in a sessions database table.

# config/initializers/session_store.rb

config.session_store :active_record_store

Pros

  • Fully persistent β€” survives server and cache restarts
  • Queryable β€” find all active sessions for a user via SQL
  • Server-side invalidation supported
  • No extra infrastructure beyond your existing database

Cons

  • A database query on every single request
  • sessions table grows indefinitely β€” needs a scheduled cleanup job
  • Slowest option under high traffic
  • Adds load to an already-busy resource

When to use

When you need to query or audit sessions (e.g. an admin "active sessions" panel), or you don't have Redis but need server-side invalidation. Always add an index on session_id and schedule a periodic cleanup.

# Recommended: clean up expired sessions periodically
Session.where('updated_at < ?', 2.weeks.ago).delete_all

4.Custom Store / Third-Party Stores

Rails also allows you to use a custom session store or integrate stores provided by third-party gems.

It must implement the following methods:

  • find_session: Retrieves session data associated with the session ID.
  • write_session: Saves the session data to your storage backend.
  • delete_session: Removes the session data from storage.
#lib/my_custom_store.rb

module ActionDispatch
  module Session
    class MyCustomStore < AbstractStore
      def find_session(req, sid)
        # Logic to find and return [sid, session_data]
      end

      def write_session(req, sid, session, options)
        # Logic to save session data
        sid # Return the session ID
      end

      def delete_session(req, sid, options)
        # Logic to destroy the session
        generate_sid # Return a new session ID
      end
    end
  end
end

#config/initializers/session_store.rb

Rails.application.config.session_store :my_custom_store

This is useful when you need:

  • Distributed session handling (microservices)
  • Better scalability
  • Custom logic (multi-region, sharding, etc.)

Session Security in Rails

Rails includes several security mechanisms to protect session data.

Signed Cookies

Rails signs cookies to prevent tampering.

If someone modifies the cookie manually, Rails will reject it.

Encrypted Cookies

Session cookies are encrypted using your application's secretkeybase.

This ensures users cannot read session data.

Session Expiration

Sessions can expire automatically.

Example:

# config/initializers/session_store.rb

Rails.application.config.session_store :cookie_store, expire_after: 30.minutes

πŸ‘‰ Encryption ensures confidentiality, while HMAC ensures integrity β€” both are required for secure sessions.

Best practices

  1. Store only the minimum β€” a user_id integer, not a full user object. Sessions are meant to store identifiers, not full data.
  2. Never store sensitive data in the session β€” use the database for that.
  3. Always call reset_session on login β€” prevents session fixation attacks.
  4. Set secure: true in production so the session cookie is HTTPS-only.
  5. Use httpOnly: true (the Rails default) to prevent JS from reading the cookie
  6. Rotate your secret_key_base periodically β€” it invalidates all existing sessions
  7. Consider same_site: :lax (default) or :strict to defend against CSRF.

Summary

  • The core concept β€” why sessions exist (HTTP is stateless)
  • The full request lifecycle β€” from browser cookie to controller and back
  • Reading & writing with session[:key], reset_session, and session.delete
  • The default CookieStore β€” what's signed vs. encrypted, and the 4 KB limit
  • All major session stores (CookieStore, cache_store via Redis, ActiveRecord, Memcache)
  • Security best practices β€” session fixation, secure:, httponly:, same_site:

Related Links