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:
- Updates the session hash with
user_id: user.id - Serializes it
- Encrypts it
- Signs it (HMAC)
- 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:
- Reads the cookie
- Splits payload and signature
- Decrypts the data
- Verifies the signature using HMAC and secretkeybase
- 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
Cookieheader 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_sessionfrom 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_baselogs 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
sessionstable 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
- Store only the minimum β a user_id integer, not a full user object. Sessions are meant to store identifiers, not full data.
- Never store sensitive data in the session β use the database for that.
- Always call reset_session on login β prevents session fixation attacks.
- Set
secure: truein production so the session cookie is HTTPS-only. - Use httpOnly: true (the Rails default) to prevent JS from reading the cookie
- Rotate your
secret_key_baseperiodically β it invalidates all existing sessions - Consider
same_site: :lax(default) or:strictto 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: