CrackedRuby CrackedRuby

Overview

HTTP operates as a stateless protocol where each request contains no inherent knowledge of previous requests. Cookies and sessions provide mechanisms to maintain state across multiple HTTP requests, enabling web applications to remember user authentication, preferences, shopping carts, and other data between page loads.

Cookies are small text files stored on the client's browser, sent with each request to the originating domain. The server sets cookies through HTTP response headers, and the browser automatically includes them in subsequent requests. Cookies persist data directly on the client side, making them accessible across browser sessions and even after the browser closes, depending on expiration settings.

Sessions store data on the server side, identified by a session ID typically transmitted via cookies. When a user first accesses an application, the server generates a unique session ID, sends it to the client as a cookie, and creates a corresponding data store on the server. Subsequent requests include the session ID cookie, allowing the server to retrieve the associated session data.

# Cookie set in HTTP response
Set-Cookie: user_id=12345; Path=/; HttpOnly; Secure

# Cookie sent in HTTP request
Cookie: user_id=12345

The distinction between cookies and sessions determines where data lives and who controls it. Cookies place data in the user's control, limited by browser storage constraints and security policies. Sessions keep data server-side, protected from client manipulation but requiring server resources for storage and retrieval.

Key Principles

Cookies function through HTTP headers exchanged between client and server. The Set-Cookie header in an HTTP response instructs the browser to store a key-value pair. The browser then includes this data in the Cookie header of subsequent requests to the same domain, subject to scope restrictions defined by attributes.

Cookie attributes control behavior and security:

Domain and Path define cookie scope. The Domain attribute specifies which hosts receive the cookie. Without explicit domain setting, the cookie applies only to the exact host that set it. The Path attribute restricts the cookie to specific URL paths within the domain.

Expiration determines cookie lifetime through Expires or Max-Age attributes. Cookies without expiration become session cookies, deleted when the browser closes. Persistent cookies remain stored until the specified expiration date.

Security flags protect cookie data. The HttpOnly flag prevents JavaScript access to cookies, mitigating cross-site scripting (XSS) attacks. The Secure flag restricts transmission to HTTPS connections only. The SameSite attribute controls cross-site request behavior, preventing certain types of cross-site request forgery (CSRF) attacks.

# Cookie with security attributes
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600

Sessions abstract storage complexity from client-side cookies. The session mechanism requires three components: a session ID generator, a storage backend, and a cookie or URL parameter for transmitting the session ID.

Session IDs must be cryptographically random to prevent prediction attacks. Weak session ID generation allows attackers to guess valid session IDs and hijack user sessions. Strong session IDs contain sufficient entropy (typically 128 bits or more) generated from cryptographically secure random number generators.

Storage backends vary in complexity and performance characteristics. In-memory storage offers speed but loses data on server restart and doesn't share sessions across multiple application servers. Database storage persists sessions and enables server clustering but introduces database query overhead. Cache-based storage (Redis, Memcached) balances performance and persistence.

Session lifecycle management requires creation, validation, and destruction logic. The application creates sessions on user login or first visit, validates session IDs on each request, and destroys sessions on logout or expiration. Session fixation attacks exploit applications that accept externally provided session IDs, so secure implementations always regenerate session IDs after authentication state changes.

Cookie size limitations restrict data storage. Browsers typically limit individual cookies to 4KB and allow approximately 50 cookies per domain. Applications storing large data structures in cookies encounter size restrictions and increased bandwidth usage as cookies transmit with every request.

Ruby Implementation

Ruby web applications handle cookies through the Rack interface, which standardizes HTTP request and response handling across frameworks. Rack represents requests as environment hashes and responses as arrays containing status, headers, and body.

Reading cookies from requests:

# Rack middleware accessing cookies
class CookieReader
  def initialize(app)
    @app = app
  end
  
  def call(env)
    request = Rack::Request.new(env)
    user_id = request.cookies['user_id']
    preferences = request.cookies['preferences']
    
    # Process request with cookie data
    @app.call(env)
  end
end

Setting cookies in responses requires manipulating the Set-Cookie header. Rack provides utilities for cookie serialization:

# Setting cookies in Rack
class CookieSetter
  def call(env)
    status = 200
    headers = {}
    
    Rack::Utils.set_cookie_header!(headers, 'user_id', {
      value: '12345',
      path: '/',
      expires: Time.now + 3600,
      secure: true,
      httponly: true,
      same_site: :strict
    })
    
    body = ['Cookie set successfully']
    [status, headers, body]
  end
end

Rails abstracts cookie management through controller methods. The cookies hash provides read and write access with automatic serialization:

# Rails cookie handling
class SessionsController < ApplicationController
  def create
    user = User.authenticate(params[:email], params[:password])
    
    if user
      # Set permanent cookie (20 years expiration)
      cookies.permanent[:user_id] = user.id
      
      # Set encrypted cookie
      cookies.encrypted[:auth_token] = {
        value: user.generate_token,
        httponly: true,
        secure: Rails.env.production?
      }
      
      redirect_to dashboard_path
    else
      render :new
    end
  end
  
  def destroy
    cookies.delete(:user_id)
    cookies.delete(:auth_token)
    redirect_to root_path
  end
end

Rails provides cookie encryption through cookies.encrypted, which automatically encrypts cookie values using the application's secret key base. This prevents clients from reading or modifying cookie contents without invalidating the signature.

Session management in Ruby typically uses Rack::Session middleware. Rack includes several session storage implementations:

# Configuring cookie-based sessions in Rack
use Rack::Session::Cookie,
  key: '_myapp_session',
  secret: 'long_random_secret_key_here',
  expire_after: 2592000  # 30 days

Cookie-based sessions store all session data in an encrypted cookie. This eliminates server-side storage requirements but limits session size to cookie size restrictions and sends session data with every request.

Rails session management operates through ActionDispatch::Session:

# Rails session configuration in config/application.rb
config.session_store :cookie_store, 
  key: '_myapp_session',
  expire_after: 30.days,
  secure: Rails.env.production?,
  httponly: true,
  same_site: :lax

# Alternative: cache-based sessions
config.session_store :cache_store,
  key: '_myapp_session',
  expire_after: 30.days

Reading and writing session data in Rails:

class ShoppingCartsController < ApplicationController
  def add_item
    session[:cart] ||= []
    session[:cart] << params[:item_id]
    
    # Session data persists across requests
    redirect_to cart_path
  end
  
  def show
    @cart_items = Item.find(session[:cart] || [])
  end
  
  def clear
    session.delete(:cart)
    redirect_to root_path
  end
end

Redis-backed sessions provide distributed session storage:

# Using Redis for session storage with redis-rails gem
# config/initializers/session_store.rb
Rails.application.config.session_store :redis_store,
  servers: ['redis://localhost:6379/0/session'],
  expire_after: 30.days,
  key: '_myapp_session',
  threadsafe: true,
  secure: Rails.env.production?

Custom session stores implement the Rack::Session::Abstract::Persisted interface:

# Custom database-backed session store
class DatabaseSessionStore < Rack::Session::Abstract::Persisted
  def get_session(env, sid)
    session = Session.find_by(session_id: sid)
    return [generate_sid, {}] unless session
    
    [sid, JSON.parse(session.data)]
  end
  
  def set_session(env, sid, session_data, options)
    Session.find_or_create_by(session_id: sid).update(
      data: session_data.to_json,
      updated_at: Time.current
    )
    sid
  end
  
  def destroy_session(env, sid, options)
    Session.find_by(session_id: sid)&.destroy
    generate_sid
  end
end

Practical Examples

Authentication systems demonstrate typical cookie and session usage patterns. User login establishes a session, logout destroys it, and middleware validates session state on protected requests.

# Authentication system using sessions
class User < ApplicationRecord
  has_secure_password
  
  def self.authenticate(email, password)
    user = find_by(email: email)
    user if user&.authenticate(password)
  end
end

class SessionsController < ApplicationController
  def create
    user = User.authenticate(params[:email], params[:password])
    
    if user
      # Prevent session fixation
      reset_session
      
      # Store user ID in session
      session[:user_id] = user.id
      session[:logged_in_at] = Time.current
      
      # Optional: remember me functionality
      if params[:remember_me]
        cookies.permanent.encrypted[:remember_token] = user.remember_token
      end
      
      redirect_to dashboard_path
    else
      flash[:error] = 'Invalid credentials'
      render :new
    end
  end
  
  def destroy
    # Clear session
    session.delete(:user_id)
    
    # Clear remember cookie
    cookies.delete(:remember_token)
    
    redirect_to root_path
  end
end

# Authentication middleware
class Authenticated
  def initialize(app)
    @app = app
  end
  
  def call(env)
    request = Rack::Request.new(env)
    session = env['rack.session']
    
    unless session[:user_id]
      return [302, {'Location' => '/login'}, []]
    end
    
    @app.call(env)
  end
end

Shopping cart implementation stores cart state across requests:

class CartItem
  attr_accessor :product_id, :quantity, :added_at
  
  def initialize(product_id, quantity = 1)
    @product_id = product_id
    @quantity = quantity
    @added_at = Time.current
  end
  
  def to_h
    { product_id: product_id, quantity: quantity, added_at: added_at }
  end
  
  def self.from_hash(hash)
    new(hash['product_id'], hash['quantity']).tap do |item|
      item.added_at = Time.parse(hash['added_at'])
    end
  end
end

class CartsController < ApplicationController
  def add
    cart = load_cart
    existing_item = cart.find { |item| item.product_id == params[:product_id].to_i }
    
    if existing_item
      existing_item.quantity += params[:quantity].to_i
    else
      cart << CartItem.new(params[:product_id].to_i, params[:quantity].to_i)
    end
    
    save_cart(cart)
    redirect_to cart_path
  end
  
  def show
    @cart = load_cart
    @products = Product.find(@cart.map(&:product_id))
  end
  
  def update
    cart = load_cart
    item = cart.find { |i| i.product_id == params[:product_id].to_i }
    item.quantity = params[:quantity].to_i if item
    
    save_cart(cart)
    redirect_to cart_path
  end
  
  private
  
  def load_cart
    session[:cart]&.map { |item_hash| CartItem.from_hash(item_hash) } || []
  end
  
  def save_cart(cart)
    session[:cart] = cart.map(&:to_h)
  end
end

Multi-step forms maintain state across multiple requests:

class RegistrationWizardController < ApplicationController
  def step_one
    # Display first form
  end
  
  def save_step_one
    session[:registration] ||= {}
    session[:registration][:personal_info] = {
      name: params[:name],
      email: params[:email],
      phone: params[:phone]
    }
    
    redirect_to registration_step_two_path
  end
  
  def step_two
    redirect_to registration_step_one_path unless session[:registration][:personal_info]
    # Display second form
  end
  
  def save_step_two
    session[:registration][:address_info] = {
      street: params[:street],
      city: params[:city],
      postal_code: params[:postal_code]
    }
    
    redirect_to registration_step_three_path
  end
  
  def step_three
    @registration_data = session[:registration]
    # Display confirmation
  end
  
  def complete
    registration_data = session[:registration]
    
    user = User.create!(
      name: registration_data[:personal_info][:name],
      email: registration_data[:personal_info][:email],
      phone: registration_data[:personal_info][:phone],
      street: registration_data[:address_info][:street],
      city: registration_data[:address_info][:city],
      postal_code: registration_data[:address_info][:postal_code]
    )
    
    session.delete(:registration)
    session[:user_id] = user.id
    
    redirect_to dashboard_path
  end
end

Security Implications

Session hijacking occurs when attackers obtain valid session IDs and impersonate authenticated users. Session IDs transmitted over unencrypted connections can be intercepted through network sniffing. The Secure flag prevents this by restricting cookies to HTTPS connections only.

# Force secure cookies in production
Rails.application.config.force_ssl = true
Rails.application.config.session_store :cookie_store,
  key: '_myapp_session',
  secure: Rails.env.production?,
  httponly: true,
  same_site: :strict

Session fixation attacks trick users into authenticating with attacker-controlled session IDs. The attacker provides a session ID to the victim (through URL parameters or by setting cookies on a vulnerable subdomain), then waits for the victim to authenticate. The application must regenerate session IDs after authentication:

def login
  user = User.authenticate(params[:email], params[:password])
  
  if user
    # Critical: regenerate session ID after authentication
    reset_session
    session[:user_id] = user.id
    redirect_to dashboard_path
  end
end

Cross-Site Scripting (XSS) attacks exploit applications that fail to sanitize user input, allowing attackers to inject JavaScript that reads cookies and sends them to attacker-controlled servers. The HttpOnly flag prevents JavaScript access to cookies:

cookies[:auth_token] = {
  value: user.auth_token,
  httponly: true,  # Prevents JavaScript access
  secure: true
}

Cross-Site Request Forgery (CSRF) attacks exploit browsers automatically sending cookies with requests. Attackers create malicious pages that submit requests to target applications, leveraging the victim's authenticated session. The SameSite attribute mitigates CSRF by controlling when browsers send cookies with cross-site requests:

# SameSite=Strict: Never send cookies with cross-site requests
cookies[:session] = {
  value: session_id,
  same_site: :strict
}

# SameSite=Lax: Send cookies with top-level navigation but not subrequests
cookies[:session] = {
  value: session_id,
  same_site: :lax
}

Cookie injection attacks exploit applications that improperly validate cookie values. Attackers modify cookie contents to inject malicious data or escalate privileges. Encrypting and signing cookies prevents tampering:

# Rails encrypted cookies prevent tampering
cookies.encrypted[:user_role] = 'admin'

# Attempting to modify encrypted cookie invalidates it
# Modified cookies result in nil values when read

Session enumeration attacks attempt to guess valid session IDs through brute force. Strong session ID generation with sufficient entropy makes enumeration computationally infeasible:

# Secure session ID generation
require 'securerandom'

def generate_session_id
  # 128 bits of entropy
  SecureRandom.hex(16)
end

Cookie overflow attacks exploit size limitations by storing excessive data in cookies, causing browsers to truncate or reject cookies. This denial of service attack disrupts application functionality. Applications should validate cookie sizes and use session storage for large data.

Performance Considerations

Cookie-based sessions transmit all session data with every request, increasing bandwidth usage. A 4KB session cookie transmitted 100 times per user session transfers 400KB of redundant data. Applications with high traffic or mobile users on metered connections should minimize cookie size.

Server-side sessions reduce bandwidth but require storage and lookup operations. Every request performs a session read, making session storage performance critical. In-memory storage provides microsecond access times but doesn't scale across multiple servers:

# In-memory session store (single server only)
class MemoryStore
  def initialize
    @store = {}
    @mutex = Mutex.new
  end
  
  def read(session_id)
    @mutex.synchronize { @store[session_id] }
  end
  
  def write(session_id, data)
    @mutex.synchronize { @store[session_id] = data }
  end
end

Cache-based session storage balances performance and scalability. Redis or Memcached provide sub-millisecond access times with built-in expiration and distribution across multiple servers:

# Redis session performance characteristics
require 'redis'

redis = Redis.new(host: 'localhost', port: 6379)

# Write: ~0.5ms average
session_id = SecureRandom.hex(16)
redis.setex("session:#{session_id}", 3600, {user_id: 123}.to_json)

# Read: ~0.3ms average
session_data = JSON.parse(redis.get("session:#{session_id}"))

Database-backed sessions introduce query overhead on every request. Indexed session ID lookups take 1-10ms depending on database load and configuration. Connection pooling and read replicas improve performance:

# Database session with connection pooling
class DatabaseSession < ApplicationRecord
  self.table_name = 'sessions'
  
  # Index on session_id for fast lookups
  # CREATE INDEX index_sessions_on_session_id ON sessions(session_id)
end

# Configure connection pool
Rails.application.config.database_configuration = {
  pool: 25,
  checkout_timeout: 5
}

Session cleanup prevents storage growth. Expired sessions accumulate without explicit deletion, consuming memory or disk space. Background jobs periodically remove expired sessions:

# Session cleanup job
class SessionCleanupJob
  def perform
    # Delete sessions inactive for 30 days
    Session.where('updated_at < ?', 30.days.ago).delete_all
    
    # Redis automatic expiration through TTL
    # Sessions expire automatically without cleanup jobs
  end
end

Cookie domain configuration affects performance through caching. Cookies set for specific subdomains reduce unnecessary cookie transmission to other subdomains:

# Set cookie only for api subdomain
cookies[:api_token] = {
  value: token,
  domain: 'api.example.com'
}

# Avoid setting cookies for entire domain when unnecessary
# Bad: domain: '.example.com' sends cookie to all subdomains

Common Pitfalls

Storing sensitive data in unencrypted cookies exposes information to users and attackers. Cookie contents are visible in browser developer tools and transmitted in plain text over HTTP. Applications must encrypt sensitive cookie data or store it server-side:

# Wrong: plaintext password in cookie
cookies[:password] = user.password

# Correct: encrypted auth token
cookies.encrypted[:auth_token] = user.generate_secure_token

Accepting user-provided session IDs enables session fixation attacks. Applications must generate session IDs server-side and reject external session IDs:

# Vulnerable: accepts session ID from params
session_id = params[:session_id] || generate_session_id

# Correct: always generates session ID server-side
session_id = generate_session_id

Failing to regenerate session IDs after authentication or privilege changes allows session fixation. The application must create new session IDs when security context changes:

# Wrong: keeps same session ID after login
def login
  if user = User.authenticate(params[:email], params[:password])
    session[:user_id] = user.id  # Vulnerable to fixation
  end
end

# Correct: regenerates session ID
def login
  if user = User.authenticate(params[:email], params[:password])
    reset_session  # Destroys old session, creates new ID
    session[:user_id] = user.id
  end
end

Setting overly broad cookie domains transmits cookies to unintended subdomains, exposing session data and increasing bandwidth usage:

# Wrong: sends cookie to all subdomains
cookies[:session] = {
  value: session_id,
  domain: '.example.com'
}

# Correct: limits to specific domain
cookies[:session] = {
  value: session_id
  # No domain attribute defaults to exact host
}

Omitting HttpOnly and Secure flags exposes cookies to JavaScript access and unencrypted transmission:

# Vulnerable: no security flags
cookies[:auth_token] = user.token

# Secure: proper flags
cookies[:auth_token] = {
  value: user.token,
  httponly: true,
  secure: true,
  same_site: :strict
}

Using weak session ID generation allows prediction attacks. Session IDs must contain sufficient cryptographic randomness:

# Wrong: predictable session IDs
session_id = "#{user.id}_#{Time.now.to_i}"

# Correct: cryptographically random
session_id = SecureRandom.hex(32)  # 256 bits entropy

Storing excessive data in sessions causes memory issues or exceeds cookie size limits. Applications should store identifiers in sessions and retrieve full objects from databases:

# Wrong: stores entire user object
session[:user] = User.find(user_id).attributes

# Correct: stores only identifier
session[:user_id] = user.id
current_user = User.find(session[:user_id])

Failing to implement session expiration allows indefinite session validity, increasing security risks. Applications must expire sessions after inactivity periods:

# Session expiration check
class ApplicationController < ActionController::Base
  before_action :validate_session_expiration
  
  private
  
  def validate_session_expiration
    if session[:last_activity_at]
      if Time.current - Time.parse(session[:last_activity_at]) > 30.minutes
        reset_session
        redirect_to login_path, alert: 'Session expired'
        return
      end
    end
    
    session[:last_activity_at] = Time.current.to_s
  end
end

Reference

Cookie Attributes

Attribute Purpose Security Impact
Domain Specifies hosts that receive cookie Broad domains increase exposure
Path Restricts cookie to URL paths Limits cookie scope
Expires Sets expiration date Persistent cookies survive browser close
Max-Age Sets lifetime in seconds Alternative to Expires
HttpOnly Blocks JavaScript access Prevents XSS cookie theft
Secure Requires HTTPS transmission Prevents network interception
SameSite Controls cross-site sending Mitigates CSRF attacks

SameSite Values

Value Behavior Use Case
Strict Never sent with cross-site requests Maximum CSRF protection
Lax Sent with top-level navigation Balance security and usability
None Always sent with cross-site requests Third-party integrations

Ruby Cookie Methods

Method Purpose Example
cookies[:key] Read or write cookie cookies[:user_id] = 123
cookies.encrypted[:key] Encrypted cookie access cookies.encrypted[:token] = secret
cookies.signed[:key] Signed cookie access cookies.signed[:user_id] = 123
cookies.permanent[:key] Set 20-year expiration cookies.permanent[:remember] = true
cookies.delete(:key) Remove cookie cookies.delete(:session)

Session Storage Comparison

Storage Type Performance Persistence Scaling Size Limit
Cookie No server overhead Client-side Automatic 4KB
Memory Fastest (μs) Volatile Single server RAM
Cache (Redis) Fast (sub-ms) Configurable Horizontal Configurable
Database Slowest (ms) Permanent Horizontal Large

Rails Session Configuration Options

Option Type Description
key String Cookie name for session ID
secret String Encryption key for cookie store
expire_after Duration Session lifetime
secure Boolean Require HTTPS
httponly Boolean Block JavaScript access
same_site Symbol CSRF protection level
domain String Cookie domain scope

Security Checklist

Requirement Implementation Prevents
HTTPS only secure: true Network interception
No JavaScript access httponly: true XSS attacks
Cross-site protection same_site: :strict CSRF attacks
Strong session IDs SecureRandom.hex(32) Session enumeration
Regenerate on login reset_session Session fixation
Session expiration expire_after: 30.minutes Indefinite access
Encrypted storage cookies.encrypted Data tampering

Common Session Patterns

Pattern Implementation Use Case
Authentication session[:user_id] = user.id User login state
Flash messages session[:flash] = message Temporary notifications
Shopping cart session[:cart_items] = items Temporary user data
Form wizard session[:step_data] = data Multi-step processes
Remember me cookies.permanent[:token] = token Persistent login