Sessions, Cookies, and Authentication in Modern Web Applications with Rails 8

This chapter provides a coherent, self-contained guide to how web applications identify users, maintain authenticated state across requests, and implement secure password handling and resets—using Rails 8’s first-class authentication tooling. We discuss HTTP’s stateless nature, the role of cookies and server-side sessions, secure password storage and verification, Rails 8’s built-in authentication generator, access control in controllers, distinctions between web and API auth, password reset flows with tokenized emails, and background jobs for responsive UX. We also interleave security best practices and address privacy and regulatory contexts around cookies.

Recording note: The class was recorded on 2026-10-21 08:35:38 (local time). Administrative details and scheduling remarks are omitted as they are not part of the technical content.

1. Why Authentication Exists: Identifying the Current User

HTTP is a stateless protocol: each request is independent and carries no built-in memory of who the client is. Yet many web features require the server to know “who the user is” across requests:

  • Public actions (e.g., viewing a catalog of equipment) can be open to anyone.
  • Protected actions (e.g., creating a rental/loan, editing an inventory item) must be restricted and attributable to a specific authenticated user.

Authentication answers the question “who are you?” and authorization answers “what are you allowed to do?” To bridge HTTP’s statelessness, we pair cookies (client-side key–value storage) with sessions (server-side memory of authentication state) so that a user signs in once and subsequent requests carry proof of authentication automatically.

2. Cookies: Definition, Mechanics, and Privacy Context

2.1 What Cookies Are and How They Work

A cookie is a small key–value pair that a server asks a browser to store and send back automatically on subsequent requests to matching domains and paths. The common login flow:

  1. The user submits credentials via POST.
  2. The server validates them and issues a Set-Cookie header.
  3. The browser stores the cookie and includes it in the Cookie header on future requests.
  4. The server reads the cookie to resume the session and identify the user.

Cookies include attributes controlling behavior and security:

  • Expires/Max-Age: controls lifetime; sessions often expire after days to reduce risk on lost devices.
  • Secure: send only over HTTPS.
  • HttpOnly: hide from JavaScript to mitigate XSS theft.
  • SameSite (Lax, Strict, None): controls cross-site sending to mitigate CSRF.

Cookies are typically small (a few kilobytes per cookie), so they add minimal overhead unless misused.

2.2 Essential vs. Marketing Cookies and Consent

Not all cookies serve the same purpose:

  • Essential cookies: Required for functionality like session IDs and carts; rejecting them typically breaks login or checkout.
  • Marketing/tracking cookies: Enable cross-site profiling and personalized ads. Users should prefer “reject all” for non-essential categories in consent banners.

Privacy regulations (e.g., the EU’s ePrivacy/GDPR) require transparency and consent for non-essential cookies. The class referenced contexts like EU and Chile’s evolving personal data protection regimes: identifiers like IP addresses, device IDs, and user agents can be personal data, prompting stricter consent and disclosure.

Practical hygiene:

  • Clear cookies periodically to reset tracking (trade-off: you’ll need to re-login and you lose saved preferences).
  • Use private browsing to reduce persistence and cross-site accumulation (note: incognito windows do accept cookies but isolate and delete them when closed).

2.3 Misconceptions and Anecdotes

  • “Private browsing does not accept cookies” is incorrect—browsers accept cookies but scope them to the session and delete them on window close.
  • Dynamic pricing (e.g., airline tickets) may use cookies as one of many signals (repeat interest, geolocation, device fingerprint). Mitigations like private browsing can help but are not universal.
  • Perceptions of “phone listening” to drive ads often stem from other signals (search history, location, social graphs). Treat microphone-based targeting as a hypothesis, not a default assumption.

3. Sessions: Server Memory Across Requests

3.1 Concept and Storage Patterns

A session is server-side state associated with a browser. The browser stores a session identifier (in a cookie), and the server uses that ID to retrieve session data:

  • Server-side store: Cookie holds only an opaque session ID; server keeps the session data in a database or Redis.
  • Encrypted cookie store: The session itself (small data) is serialized, signed, and optionally encrypted, and stored entirely in the cookie.

Rails protects cookie integrity by signing (and optionally encrypting) values so clients cannot tamper with them undetected. If a cookie is altered, signature verification fails and Rails rejects it.

Typical schema:

  • users table:
    • id, email_address, password_digest (bcrypt hash)
  • sessions table:
    • id, user_id, ip_address, user_agent, timestamps

A session’s metadata (IP, user agent) supports auditing and anomaly detection.

3.2 Cookie Protection: Encoding, Signing, Encryption

  • Base64 encoding: Represent binary as text; not a security mechanism on its own.
  • Signing (MAC): Prevents tampering; if any byte changes, signature validation fails.
  • Encryption: Protects confidentiality of cookie contents.

Rails composes these layers so cookies can be safely used for session continuity over HTTPS with Secure and HttpOnly flags set.

4. Passwords: Secure Storage and Verification

4.1 Never Store Plaintext

Plaintext password storage is a critical vulnerability. If a breach occurs, attackers gain immediate access locally and potentially across other sites due to password reuse. A red flag: any site that can email you your original password must be storing it in plaintext or reversible form.

4.2 Password Hashing Properties

Modern password storage uses purpose-built, slow hash functions with salts:

  • Non-invertible: No practical way to recover the password from its hash.
  • Deterministic: Given the same input and salt, result is the same.
  • Salted: Unique salts ensure identical passwords produce different hashes, defeating rainbow tables and correlation across users.
  • Deliberately slow: Functions like bcrypt impose computational cost to slow brute-force attacks.

Rails integrates bcrypt by default.

4.3 How Verification Works

  • Registration:
    • Server salts and hashes the password and stores password_digest; never stores the plaintext.
  • Login:
    • Server retrieves the user by email and asks bcrypt to verify the submitted password against the stored digest.

4.4 Rails Support: has_secure_password

In your User model:

class User < ApplicationRecord
has_secure_password
end
  • Adds password=, password_confirmation=, and authentication helpers.
  • Stores password_digest (bcrypt format).
  • Example:
user = User.create(email_address: "[email protected]", password: "correct horse battery staple")
user.authenticate("candidate_password") # returns user on success, false otherwise
User.authenticate_by(email_address: "[email protected]", password: "secret") # combined lookup + verify

5. Rails 8 Built-In Authentication: Sessions as a First-Class Resource

Rails 8 introduces a native generator that scaffolds authentication without external gems like Devise for common cases. It models sessions as a RESTful resource and enforces security by default across controllers.

5.1 Generator and Artifacts

Run:

bin/rails generate authentication

Generated components typically include:

  • Models:
    • User (has_secure_password; has_many :sessions)
    • Session (belongs_to :user, stores id/token, ip_address, user_agent, timestamps)
    • Current (request-scoped attributes for the active session and derived user)
  • Migrations: for users and sessions.
  • Concerns:
    • Authentication (helpers to require auth, start/end sessions, resume sessions)
  • Controllers:
    • SessionsController (new/create/destroy)
    • PasswordsController (for “forgot password” flows)
  • Mailers: PasswordMailer (or PasswordsMailer) for reset emails
  • Routes: Sign-in, sign-out, and password recovery routes

5.2 The User and Session Models

class User < ApplicationRecord
has_secure_password
has_many :sessions, dependent: :destroy
end
class Session < ApplicationRecord
belongs_to :user
# Attributes: id or token, user_agent, ip_address, created_at, etc.
end

Two idiomatic ways to create associated sessions:

Session.create(user: user, user_agent: request.user_agent, ip_address: request.remote_ip)
user.sessions.create(user_agent: request.user_agent, ip_address: request.remote_ip) # concise, via association

5.3 Current: Request-Scoped State

Rails uses a request-local object to hold the current session and expose the current user:

class Current < ActiveSupport::CurrentAttributes
attribute :session
delegate :user, to: :session, allow_nil: true
end
  • No database table.
  • Reset between requests.
  • Available across controllers and views.

5.4 Starting, Resuming, and Ending Sessions

Sign-in flow in SessionsController:

class SessionsController < ApplicationController
allow_unauthenticated_access only: [:new, :create]
def create
user = User.authenticate_by(params.permit(:email_address, :password))
if user
start_new_session_for(user)
redirect_to after_authentication_url
else
redirect_to new_session_path, notice: "Try another email address or password"
end
end
end

Starting a session:

module Authentication
def start_new_session_for(user)
session = user.sessions.create!(
user_agent: request.user_agent,
ip_address: request.remote_ip
)
Current.session = session
cookies.signed[:session_id] = {
value: session.id,
httponly: true,
same_site: :lax,
permanent: true
}
end
end

Resuming or requiring auth:

module Authentication
def require_authentication
resume_session || request_authentication
end
def resume_session
return Current.session if Current.session
sid = cookies.signed[:session_id]
Current.session = Session.find_by(id: sid) if sid
end
def request_authentication
session[:return_to] = after_authenticating_return_to(request)
redirect_to new_session_path
end
end

Signing out:

module Authentication
def terminate_session
Current.session&.destroy
cookies.delete(:session_id)
Current.session = nil
end
end

5.5 UI Hooks: RESTful Session Actions

In views:

<!-- erb -->
<% if authenticated? %>
<%= button_to "Sign out", session_path, method: :delete %>
<% else %>
<%= link_to "Sign in", new_session_path %>
<% end %>
  • Use button_to with method: :delete for sign-out (HTML anchors issue GET by default).

6. Access Control: Secure-by-Default Controllers

By default, ApplicationController includes authentication checks that run before each action, protecting all controllers inheriting from it.

  • Public endpoints: Use allow_unauthenticated_access selectively.
class UnitsController < ApplicationController
allow_unauthenticated_access only: [:index, :show]
def index
@units = Unit.available
end
def show
@unit = Unit.find(params[:id])
end
def edit
@unit = Unit.find(params[:id]) # protected by default
end
end
  • Protected resources (e.g., LoansController) require authentication by default. When creating records, tie them to the current user:
class LoansController < ApplicationController
def create
@loan = Current.user.loans.build(loan_params)
# persistence logic...
end
end

6.1 Web vs. API Authentication

Web controllers (ActionController::Base) use cookie-based sessions. API controllers (ActionController::API) are stateless and should use bearer tokens in headers.

  • Web controllers:
    • Cookie-backed sessions; allow_unauthenticated_access applies.
  • API controllers:
    • No cookie lifecycle; implement explicit token checks (e.g., authenticate_with_http_token).
    • Macros designed for web sessions don’t apply.

7. Registration: Why Rails Leaves It To You

Rails 8’s generator focuses on session management (sign-in/out) and password recovery. Registration flows vary widely—simple email/password, enterprise SSO, invitations, multi-tenant onboarding—so Rails provides building blocks and leaves the controller flow to the domain.

A simple RegistrationsController:

class RegistrationsController < ApplicationController
allow_unauthenticated_access only: [:new, :create]
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
start_new_session_for @user
redirect_to root_path, notice: "Welcome! Your account has been created."
else
render :new, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email_address, :password, :password_confirmation)
end
end

Strong parameters ensure only whitelisted attributes are accepted, preventing mass-assignment vulnerabilities.

8. Password Reset: Tokenized, Signed, and Time-Limited

Password resets must be secure and user-friendly, typically implemented with signed, expiring tokens sent via email.

8.1 Requesting a Reset

  • The user submits their email.
  • Always respond ambiguously (e.g., “If the address exists, we’ll send instructions”) to avoid confirming account existence (user enumeration risk).

8.2 Generating and Sending the Token

  • Generate a signed token scoped to the user and expiring soon (e.g., 15 minutes).
  • Include the token in a link like /passwords/token/edit?token=... and email it.

Mailer definition:

# app/mailers/passwords_mailer.rb
class PasswordsMailer < ApplicationMailer
def reset(user)
@user = user
mail(
to: @user.email,
subject: "Reset your password"
)
end
end

Mailer view:

<!-- erb -->
<!-- app/views/passwords_mailer/reset.html.erb -->
<p>Hello <%= @user.name %>,</p>
<p>We received a request to reset your password. If you made this request, click the link below:</p>
<p>
<a href="<%= edit_password_url(token: @user.reset_token) %>">
Reset your password
</a>
</p>
<p>This link will expire in 15 minutes. If you didn’t request a password reset, you can ignore this email.</p>

Enqueueing the email:

if user
PasswordsMailer.reset(user).deliver_later
end
flash[:notice] = "If the address exists in our database, we will send you reset instructions."
redirect_to root_path

8.3 Handling the Reset Link and Submitting New Passwords

Controller sketch:

# app/controllers/passwords_controller.rb
def edit
@user = User.find_by_reset_token(params[:token])
if @user&.reset_token_valid?
# render form
else
redirect_to new_password_path, alert: "The reset link is invalid or has expired."
end
end
def update
@user = User.find_by_reset_token(params[:token])
if @user&.reset_token_valid? && @user.update(password_params)
redirect_to signin_path, notice: "Your password has been reset."
else
render :edit, status: :unprocessable_entity
end
end

Security traits:

  • Signed tokens provide integrity.
  • Expiration limits attack windows.
  • Ambiguous responses prevent user enumeration.

9. Email Delivery and Background Jobs: Keeping UX Fast

Email delivery involves network calls to external services and can be slow. To keep HTTP responses snappy, offload email sending and other heavy tasks to background jobs.

9.1 Mailers and Providers

  • Mailers are classes (subclassing ApplicationMailer) that render email views.
  • Providers like SendGrid handle SMTP/API delivery, bounces, and deliverability.

9.2 deliver_now vs deliver_later

  • deliver_now: sends immediately, blocks the request.
  • deliver_later: enqueues a job for asynchronous delivery; the HTTP response returns quickly.

9.3 Rails 8 Defaults: Active Job + Solid Queue

Rails 8 ships with Active Job and Solid Queue by default:

  • Active Job: framework abstraction for jobs.
  • Solid Queue: a queue backend persisting jobs in the app’s database, eliminating the need for external systems for many apps.

Example job:

# app/jobs/reminder_job.rb
class ReminderJob < ApplicationJob
queue_as :default
def perform(loan)
LoansMailer.due_tomorrow(loan).deliver_now
end
end

Enqueue:

ReminderJob.perform_later(loan)

Operational considerations:

  • Ensure a worker process runs to drain the queue.
  • Monitor job tables/dashboards.
  • Configure provider credentials and sender domains per environment.

9.4 Beyond Email: Any Slow Task

Background jobs apply to compute-heavy tasks like batch payroll computation. The pattern:

  • Return an immediate response after scheduling work.
  • Perform processing in the background.
  • Notify users upon completion (e.g., via email).

10. Security Practices and UX Considerations

  • Cookie flags: Use Secure, HttpOnly, and SameSite=Lax (or Strict) for session cookies.
  • Integrity and confidentiality: Sign cookies and encrypt when storing data client-side; prefer opaque IDs in cookies and load sensitive data server-side.
  • CSRF defense: SameSite helps; complement with authenticity tokens and appropriate CORS where applicable.
  • Error messaging: Use “Try another email address or password” to avoid disclosing account existence.
  • Session hygiene: Expire sessions reasonably; invalidate on logout; store minimal data in cookies.
  • Secrets management: Rotate secrets used for cookie signing and encryption; manage keys securely.

11. Relationships Between Concepts

  • HTTP statelessness necessitates cookies and sessions to maintain authenticated context across requests.
  • Cookies carry proof of authentication (e.g., session ID); sessions store server-side state tying requests to users.
  • Password hashing with bcrypt (via has_secure_password) secures stored credentials.
  • Rails 8’s authentication generator models sessions as RESTful resources, enforces access control at ApplicationController, and exposes request-scoped Current attributes to controllers and views.
  • Web and API controllers differ in state handling: cookie-based sessions vs. explicit bearer tokens.
  • Password resets rely on signed, expiring tokens and email delivery, which should be offloaded to background jobs for responsiveness.

12. Concrete Examples Referenced in Class

  • Rental/loan system:
    • Viewing available equipment is public.
    • Creating a rental requires sign-in to attribute the action to a specific user.
  • Session metadata:
    • Store IP address and user agent for auditing/anomaly detection.
  • Pricing anecdotes:
    • Airlines may adapt prices based on repeated interest; cookies are one of many signals.
  • Cookie inspection:
    • Developers can inspect cookies in browser DevTools.
  • Base64 note:
    • Used to encode binary as text; not a security mechanism.