CrackedRuby CrackedRuby

Cross-Site Request Forgery (CSRF)

Overview

Cross-Site Request Forgery (CSRF) attacks exploit the trust a web application has in a user's browser. When a user authenticates with a web application, the browser stores credentials (typically session cookies) and automatically includes them with subsequent requests to that domain. CSRF attacks abuse this automatic credential inclusion by tricking the user's browser into making requests the user did not intend.

The attack succeeds because web applications cannot distinguish between requests the user intended to make and requests triggered by malicious third-party sites. If a user visits an attacker's website while authenticated to a target application, the attacker can craft requests that the browser will automatically authenticate using the victim's valid session.

CSRF differs from Cross-Site Scripting (XSS) attacks. XSS involves injecting malicious scripts into a trusted site, while CSRF tricks the browser into making requests from an external site. CSRF attacks require the victim to be authenticated and typically target state-changing operations like fund transfers, password changes, or email updates.

The impact ranges from minor annoyances to severe security breaches. An attacker could transfer funds, modify account settings, make purchases, or execute any action the authenticated user has permission to perform. The attack requires no malware installation and leaves minimal traces, making detection difficult.

# Vulnerable endpoint - accepts GET request for state change
class BankController < ApplicationController
  def transfer
    account = params[:to_account]
    amount = params[:amount]
    
    current_user.transfer_funds(account, amount)
    redirect_to dashboard_path
  end
end

# Attacker's malicious page
# <img src="https://bank.com/transfer?to_account=attacker&amount=1000">
# User's browser automatically includes session cookie

Key Principles

CSRF attacks succeed through three fundamental mechanisms: automatic credential inclusion, lack of request origin verification, and execution of state-changing operations. Understanding these principles clarifies both the attack vector and defense strategies.

Automatic Credential Inclusion

Browsers automatically include credentials (cookies, HTTP authentication headers) with every request to a domain, regardless of where the request originated. This behavior enables single sign-on and session persistence but creates the CSRF vulnerability. When a user loads a page containing a malicious request, the browser dutifully attaches valid credentials.

# User authenticated to bank.com
# Session cookie: session_id=abc123

# Attacker's site triggers request
# Browser automatically includes: Cookie: session_id=abc123
# Bank application sees valid session and processes request

Same-Origin Policy Limitations

The Same-Origin Policy (SOP) prevents JavaScript on one origin from reading responses from another origin. However, SOP does not prevent requests from being sent—it only restricts reading the response. The browser will still execute the request with credentials, and state changes occur on the server even if the attacker cannot read the response.

# Attacker's JavaScript (on evil.com)
fetch('https://bank.com/transfer', {
  method: 'POST',
  credentials: 'include',
  body: 'to_account=attacker&amount=1000'
})
# Request sent with cookies
# Response blocked by SOP
# But damage already done - transfer executed

State-Changing Operations

CSRF attacks target operations that modify server state. Read-only operations pose less risk because the attacker cannot access the response due to SOP. State-changing operations include creating records, updating data, deleting resources, changing configurations, or triggering workflows.

Request Verification

Effective CSRF protection requires verifying that requests originated from the application itself, not from external sites. This verification uses secrets that attackers cannot access or predict. The server includes an unpredictable token in forms and validates it on submission. Attackers on external domains cannot read this token due to SOP.

HTTP Method Semantics

The HTTP specification defines GET as safe and idempotent—it should not modify server state. Applications that use GET for state changes become more vulnerable to CSRF because attackers can trigger GET requests through simple HTML elements like images or links, without requiring form submission or JavaScript.

# Vulnerable - GET modifies state
get '/delete_account' do
  current_user.destroy
end

# Attacker triggers with image tag
# <img src="https://app.com/delete_account">

# Safer - POST requires form or JavaScript
post '/delete_account' do
  current_user.destroy
end
# Cannot trigger with simple HTML element

Security Implications

CSRF vulnerabilities expose applications to unauthorized actions executed with legitimate user credentials. The attack requires no special access beyond tricking an authenticated user into visiting a malicious page, making it accessible to relatively unsophisticated attackers.

Authentication Bypass

CSRF bypasses authentication by abusing existing valid sessions. The application correctly verifies the user's identity through session cookies but fails to verify request intent. From the server's perspective, the malicious request appears identical to legitimate user actions.

Privilege Escalation

When CSRF targets administrative functions, attackers can perform privileged operations. An attacker who tricks an administrator into triggering a request can create new admin accounts, modify permissions, or access sensitive data. The attack inherits all privileges of the authenticated victim.

# Admin function vulnerable to CSRF
class AdminController < ApplicationController
  before_action :require_admin
  
  def create_admin
    User.create!(
      email: params[:email],
      admin: true
    )
  end
end

# Attacker creates admin account
# <form action="https://app.com/admin/create_admin" method="POST">
#   <input name="email" value="attacker@evil.com">
# </form>
# <script>document.forms[0].submit()</script>

Data Exfiltration Through Side Effects

While attackers cannot read CSRF response data, they can infer information through observable side effects. Timing differences, error messages leaked through redirects, or changes in application behavior reveal information about the operation's success or failure.

API Endpoint Exposure

JSON APIs face CSRF risks when they accept cookie-based authentication. Single-page applications that authenticate with cookies remain vulnerable. The Content-Type header provides partial protection since simple requests can use application/x-www-form-urlencoded, but JSON requests require a preflight check.

# JSON API vulnerable if using cookie auth
class ApiController < ApplicationController
  def update_profile
    current_user.update!(profile_params)
    render json: { status: 'success' }
  end
end

# Attacker can still modify data
# Response blocked by SOP but state change occurs

Subdomain Attacks

CSRF protection often relies on validating the Origin or Referer headers. However, compromised subdomains or permissive CORS policies can weaken these defenses. An attacker controlling a subdomain can set document.domain to match the parent domain in some scenarios.

Mobile Application Risks

Mobile applications using WebViews with cookie-based authentication inherit CSRF vulnerabilities. The WebView automatically includes cookies, and users may not realize they're viewing untrusted content within the application context.

Ruby Implementation

Rails provides built-in CSRF protection through the synchronizer token pattern. The framework generates unique tokens per session and validates them on state-changing requests. This implementation operates transparently for most applications but requires understanding for proper configuration.

Default Protection Mechanism

Rails includes CSRF protection automatically in ApplicationController through the protect_from_forgery method. The framework generates an authenticity token stored in the session and embedded in forms. On POST, PUT, PATCH, and DELETE requests, Rails validates the submitted token against the session value.

class ApplicationController < ActionController::Base
  # Default protection enabled
  protect_from_forgery with: :exception
  
  # Alternative strategies:
  # protect_from_forgery with: :null_session  # Clear session on failure
  # protect_from_forgery with: :reset_session # Reset entire session
end

Token Generation and Validation

Rails automatically injects CSRF tokens into forms through form helpers. The form_with and form_for helpers include a hidden field containing the authenticity token. For manual forms or AJAX requests, access the token through form_authenticity_token.

# Automatic token injection
<%= form_with model: @user do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>
# Renders: <input type="hidden" name="authenticity_token" value="...">

# Manual token inclusion
<form action="/users" method="post">
  <input type="hidden" name="authenticity_token" 
         value="<%= form_authenticity_token %>">
  <input type="text" name="user[name]">
  <input type="submit">
</form>

# JavaScript access for AJAX
document.querySelector('meta[name="csrf-token"]').content

Meta Tag Configuration

Rails includes the CSRF token in a meta tag for JavaScript access. AJAX libraries can read this token and include it in request headers. The UJS (Unobtrusive JavaScript) library handles this automatically for Rails-generated AJAX requests.

# Application layout
<head>
  <%= csrf_meta_tags %>
</head>
# Renders: <meta name="csrf-token" content="...">

# JavaScript AJAX request
const token = document.querySelector('meta[name="csrf-token"]').content

fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': token
  },
  body: JSON.stringify({ name: 'Alice' })
})

API Controller Configuration

API controllers typically use token-based authentication (JWT, OAuth) instead of session cookies, making them immune to CSRF attacks. Disable CSRF protection for API-only controllers to avoid validation errors.

class Api::BaseController < ActionController::API
  # ActionController::API excludes CSRF protection by default
  # No protect_from_forgery needed
end

# Mixed controllers serving both HTML and JSON
class UsersController < ApplicationController
  protect_from_forgery with: :exception, unless: -> { request.format.json? }
  
  # Or skip for specific actions
  skip_before_action :verify_authenticity_token, only: [:webhook]
end

Custom Token Validation

Applications with complex authentication schemes may need custom CSRF validation logic. Override verified_request? to implement custom verification while maintaining Rails' protection framework.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  
  private
  
  def verified_request?
    super || valid_api_key? || valid_jwt_token?
  end
  
  def valid_api_key?
    request.headers['X-API-Key'] == current_user&.api_key
  end
  
  def valid_jwt_token?
    # JWT verification logic
    token = request.headers['Authorization']&.sub(/^Bearer /, '')
    decoded = JWT.decode(token, secret_key) rescue nil
    decoded.present?
  end
end

Per-Form Tokens

Rails supports per-form tokens for additional security. Each form receives a unique token, preventing token reuse across different forms. Enable this feature in configuration.

# config/application.rb
config.action_controller.per_form_csrf_tokens = true

# Now each form gets unique token
<%= form_with model: @user %>  # Token specific to users form
<%= form_with model: @post %>  # Different token for posts form

Origin Checking

Rails validates the Origin header when present, providing defense in depth. Configure allowed origins for cross-origin requests if needed.

# config/application.rb
config.action_controller.forgery_protection_origin_check = true

# Only accept requests from these origins
# Useful for mobile apps or specific subdomains
Rails.application.config.hosts << "mobile.example.com"

Practical Examples

Basic Form Protection

A user profile update form demonstrates standard CSRF protection. Rails automatically includes the token, and the server validates it before processing the request.

# Controller
class ProfilesController < ApplicationController
  before_action :authenticate_user!
  
  def edit
    @user = current_user
  end
  
  def update
    @user = current_user
    # CSRF token validated automatically before this executes
    if @user.update(profile_params)
      redirect_to profile_path, notice: 'Profile updated'
    else
      render :edit
    end
  end
  
  private
  
  def profile_params
    params.require(:user).permit(:name, :email, :bio)
  end
end

# View (edit.html.erb)
<%= form_with model: @user, url: profile_path, method: :patch do |f| %>
  <%= f.label :name %>
  <%= f.text_field :name %>
  
  <%= f.label :email %>
  <%= f.email_field :email %>
  
  <%= f.label :bio %>
  <%= f.text_area :bio %>
  
  <%= f.submit 'Update Profile' %>
<% end %>

# Without CSRF protection, attacker could:
# <form action="https://app.com/profile" method="POST">
#   <input name="user[email]" value="attacker@evil.com">
# </form>
# <script>document.forms[0].submit()</script>
# With protection, this fails due to missing/invalid token

AJAX Request Protection

Single-page applications make extensive AJAX requests requiring CSRF tokens. Rails UJS handles this automatically, but custom JavaScript needs explicit token inclusion.

# Controller action
class TasksController < ApplicationController
  def create
    @task = current_user.tasks.build(task_params)
    if @task.save
      render json: @task, status: :created
    else
      render json: @task.errors, status: :unprocessable_entity
    end
  end
  
  private
  
  def task_params
    params.require(:task).permit(:title, :description)
  end
end

# JavaScript (using Fetch API)
class TaskManager {
  constructor() {
    this.csrfToken = document.querySelector('meta[name="csrf-token"]').content
  }
  
  async createTask(taskData) {
    const response = await fetch('/tasks', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': this.csrfToken
      },
      body: JSON.stringify({ task: taskData })
    })
    
    if (!response.ok) {
      throw new Error('Failed to create task')
    }
    
    return response.json()
  }
}

// Usage
const manager = new TaskManager()
manager.createTask({
  title: 'Complete documentation',
  description: 'Write CSRF guide'
})

API Endpoint with Token Authentication

APIs using JWT or OAuth tokens instead of session cookies avoid CSRF vulnerabilities. The token must be explicitly included in the Authorization header, which attackers cannot access cross-origin.

class Api::V1::BaseController < ActionController::API
  before_action :authenticate_user!
  
  private
  
  def authenticate_user!
    token = request.headers['Authorization']&.sub(/^Bearer /, '')
    
    begin
      payload = JWT.decode(token, Rails.application.secret_key_base, true, { algorithm: 'HS256' })
      @current_user = User.find(payload[0]['user_id'])
    rescue JWT::DecodeError, ActiveRecord::RecordNotFound
      render json: { error: 'Unauthorized' }, status: :unauthorized
    end
  end
  
  def current_user
    @current_user
  end
end

class Api::V1::PostsController < Api::V1::BaseController
  def create
    @post = current_user.posts.build(post_params)
    
    if @post.save
      render json: @post, status: :created
    else
      render json: { errors: @post.errors }, status: :unprocessable_entity
    end
  end
  
  private
  
  def post_params
    params.require(:post).permit(:title, :content)
  end
end

# Client request
# fetch('/api/v1/posts', {
#   method: 'POST',
#   headers: {
#     'Authorization': 'Bearer eyJhbGc...',
#     'Content-Type': 'application/json'
#   },
#   body: JSON.stringify({ post: { title: 'New Post', content: '...' } })
# })
# CSRF-safe: attacker cannot access JWT from cross-origin

Financial Transaction Protection

High-value operations like fund transfers require additional verification beyond standard CSRF tokens. Implement re-authentication or confirmation tokens for critical actions.

class TransfersController < ApplicationController
  before_action :authenticate_user!
  before_action :require_recent_authentication, only: [:create]
  
  def new
    @transfer = Transfer.new
    @accounts = current_user.accounts
  end
  
  def create
    @transfer = current_user.transfers.build(transfer_params)
    
    # Verify confirmation token
    unless valid_confirmation_token?
      flash[:error] = 'Invalid confirmation token'
      render :new
      return
    end
    
    if @transfer.save
      TransferMailer.confirmation_email(@transfer).deliver_later
      redirect_to transfers_path, notice: 'Transfer completed'
    else
      render :new
    end
  end
  
  private
  
  def transfer_params
    params.require(:transfer).permit(:from_account_id, :to_account_id, :amount)
  end
  
  def require_recent_authentication
    if session[:authenticated_at] < 5.minutes.ago
      redirect_to reauthenticate_path, alert: 'Please re-authenticate'
    end
  end
  
  def valid_confirmation_token?
    expected = session[:transfer_confirmation_token]
    actual = params[:confirmation_token]
    
    session.delete(:transfer_confirmation_token)
    expected.present? && ActiveSupport::SecurityUtils.secure_compare(expected, actual)
  end
end

# View includes confirmation token
# Generated when form loads and validated on submit
<%= form_with model: @transfer do |f| %>
  <%= hidden_field_tag :confirmation_token, generate_confirmation_token %>
  <%= f.collection_select :from_account_id, @accounts, :id, :name %>
  <%= f.collection_select :to_account_id, @accounts, :id, :name %>
  <%= f.number_field :amount %>
  <%= f.submit 'Transfer Funds' %>
<% end %>

# Helper method
def generate_confirmation_token
  token = SecureRandom.hex(32)
  session[:transfer_confirmation_token] = token
  token
end

Common Patterns

Synchronizer Token Pattern

The synchronizer token pattern generates a unique, unpredictable token per session and includes it in forms or request headers. The server validates the token on each state-changing request. Rails implements this pattern by default.

# Token generation (simplified Rails implementation)
class CsrfProtection
  def initialize(app)
    @app = app
  end
  
  def call(env)
    request = ActionDispatch::Request.new(env)
    
    # Generate token if needed
    if request.session[:csrf_token].nil?
      request.session[:csrf_token] = SecureRandom.base64(32)
    end
    
    # Validate on state-changing requests
    if state_changing_request?(request)
      unless valid_token?(request)
        return [403, {}, ['CSRF token validation failed']]
      end
    end
    
    @app.call(env)
  end
  
  private
  
  def state_changing_request?(request)
    !request.get? && !request.head? && !request.options?
  end
  
  def valid_token?(request)
    token = request.headers['X-CSRF-Token'] || 
            request.params['authenticity_token']
    
    token.present? && 
      ActiveSupport::SecurityUtils.secure_compare(
        token,
        request.session[:csrf_token]
      )
  end
end

Double-Submit Cookie Pattern

The double-submit cookie pattern stores the CSRF token in a cookie and requires clients to submit it separately in a header or form field. The server validates that both values match. This pattern works without server-side state.

class DoubleSubmitCsrfProtection
  def initialize(app)
    @app = app
  end
  
  def call(env)
    request = ActionDispatch::Request.new(env)
    
    # Set CSRF cookie if missing
    unless request.cookies['csrf_token']
      token = SecureRandom.base64(32)
      response = @app.call(env)
      Rack::Utils.set_cookie_header!(
        response[1],
        'csrf_token',
        {
          value: token,
          httponly: false, # JavaScript needs to read it
          secure: true,
          same_site: :strict
        }
      )
      return response
    end
    
    # Validate on state-changing requests
    if state_changing_request?(request)
      cookie_token = request.cookies['csrf_token']
      header_token = request.headers['X-CSRF-Token']
      
      unless cookie_token && header_token && 
             ActiveSupport::SecurityUtils.secure_compare(cookie_token, header_token)
        return [403, {}, ['CSRF validation failed']]
      end
    end
    
    @app.call(env)
  end
  
  private
  
  def state_changing_request?(request)
    !request.get? && !request.head?
  end
end

# Client-side JavaScript
const csrfToken = document.cookie
  .split('; ')
  .find(row => row.startsWith('csrf_token='))
  ?.split('=')[1]

fetch('/api/data', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': csrfToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ data: 'value' })
})

SameSite Cookie Attribute

The SameSite cookie attribute prevents browsers from sending cookies with cross-site requests. Setting SameSite to Strict or Lax provides CSRF protection without requiring tokens for many scenarios.

# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
  key: '_app_session',
  same_site: :lax, # or :strict
  secure: Rails.env.production?, # HTTPS only in production
  httponly: true

# :strict - Never send cookie cross-site
# Most secure but breaks some legitimate flows

# :lax - Send cookie on safe cross-site requests (GET)
# Balances security and usability

# :none - Always send cookie (requires secure: true)
# Use only when necessary for cross-site functionality

Custom Request Header Validation

Custom headers require JavaScript and cannot be set by simple HTML forms. Requiring a custom header on state-changing requests prevents CSRF from basic attacks. Combine with token validation for defense in depth.

class CustomHeaderProtection < ApplicationController
  before_action :verify_custom_header, except: [:index, :show]
  
  private
  
  def verify_custom_header
    unless request.headers['X-Requested-With'] == 'XMLHttpRequest' ||
           request.headers['X-Custom-Header'] == 'AppClient'
      head :forbidden
    end
  end
end

# Client includes custom header
fetch('/api/resource', {
  method: 'POST',
  headers: {
    'X-Custom-Header': 'AppClient',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ data: 'value' })
})

# Attacker cannot set custom headers cross-origin
# Browser preflight request will fail

Origin and Referer Validation

Validating the Origin or Referer headers confirms requests originate from the expected domain. This provides additional protection but should not be the sole defense, as proxies or browser extensions may strip these headers.

class OriginValidation < ApplicationController
  before_action :validate_origin, unless: -> { request.get? }
  
  private
  
  def validate_origin
    origin = request.headers['Origin'] || request.headers['Referer']
    
    unless valid_origin?(origin)
      Rails.logger.warn("Invalid origin: #{origin} for #{request.path}")
      head :forbidden
    end
  end
  
  def valid_origin?(origin)
    return false if origin.blank?
    
    uri = URI.parse(origin)
    allowed_hosts = [
      request.host,
      'mobile.example.com',
      'app.example.com'
    ]
    
    allowed_hosts.include?(uri.host)
  rescue URI::InvalidURIError
    false
  end
end

Common Pitfalls

GET Requests for State Changes

Applications that use GET requests for state-changing operations face heightened CSRF risk. Attackers can trigger GET requests through images, links, or scripts without requiring forms or JavaScript.

# Vulnerable pattern
get '/delete_account' do
  current_user.destroy
  redirect_to root_path
end

# Exploited with simple image tag
# <img src="https://app.com/delete_account">

# Correct approach
delete '/account' do
  # Protected by CSRF token
  current_user.destroy
  redirect_to root_path
end

Disabling CSRF Protection Globally

Disabling CSRF protection across the entire application to fix specific issues creates widespread vulnerabilities. Disable protection selectively only for specific actions that genuinely do not require it.

# Dangerous - disables protection everywhere
class ApplicationController < ActionController::Base
  skip_before_action :verify_authenticity_token
end

# Better - disable only where necessary
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token, only: [:github_webhook]
  before_action :verify_github_signature, only: [:github_webhook]
  
  def github_webhook
    # Webhook verified through signature instead of CSRF token
  end
end

Ignoring AJAX Request Protection

Single-page applications must include CSRF tokens in AJAX requests. Forgetting to add the token header causes validation failures or creates vulnerabilities if protection is disabled.

# Vulnerable - no CSRF token included
fetch('/api/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'New Post' })
})
// Request fails CSRF validation

# Correct - include CSRF token
const token = document.querySelector('meta[name="csrf-token"]').content
fetch('/api/posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': token
  },
  body: JSON.stringify({ title: 'New Post' })
})

Token Leakage in URLs

Embedding CSRF tokens in URLs exposes them through browser history, server logs, and Referer headers. Tokens belong in form fields or request headers, never in URLs.

# Vulnerable - token in URL
link_to 'Delete', post_path(@post, authenticity_token: form_authenticity_token), 
        method: :delete

# URL: /posts/1?authenticity_token=abc123
# Token leaked in logs and browser history

# Correct - use data attributes for UJS
link_to 'Delete', post_path(@post), method: :delete, data: { confirm: 'Sure?' }
# UJS reads token from meta tag and includes in request

Subdomain Cookie Scope

Setting cookies with overly broad domain scope allows subdomains to access the session cookie. A compromised subdomain could read session cookies and craft malicious requests.

# Vulnerable - cookie accessible to all subdomains
Rails.application.config.session_store :cookie_store,
  key: '_app_session',
  domain: '.example.com' # All subdomains can read

# Safer - limit to specific host
Rails.application.config.session_store :cookie_store,
  key: '_app_session',
  domain: 'app.example.com' # Only this host

Mixing Authentication Methods

Applications supporting both cookie-based and token-based authentication must ensure CSRF protection applies appropriately to each. Cookie-authenticated requests require CSRF tokens; token-authenticated requests do not.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception, unless: :api_request?
  
  private
  
  def api_request?
    # Skip CSRF for JWT authentication
    request.headers['Authorization'].present?
  end
  
  # Problem: attacker could send Authorization header
  # with invalid token to bypass CSRF check
end

# Better approach
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  
  private
  
  def verified_request?
    super || valid_api_authentication?
  end
  
  def valid_api_authentication?
    token = request.headers['Authorization']&.sub(/^Bearer /, '')
    return false if token.blank?
    
    JWT.decode(token, secret_key) rescue false
  end
end

Caching Pages with Tokens

Caching pages that include CSRF tokens can serve stale tokens to users. The cached token may not match the user's session, causing validation failures. Exclude token fields from cache or generate tokens dynamically.

# Problem: cached form has outdated token
class PostsController < ApplicationController
  caches_page :new
  
  def new
    @post = Post.new
  end
end

# Solution: disable caching for pages with forms
class PostsController < ApplicationController
  def new
    @post = Post.new
    expires_now # Prevent caching
  end
end

# Or use JavaScript to inject fresh token
# <form id="post-form">
#   <!-- static form fields -->
# </form>
# <script>
#   const form = document.getElementById('post-form')
#   const token = document.querySelector('meta[name="csrf-token"]').content
#   const input = document.createElement('input')
#   input.type = 'hidden'
#   input.name = 'authenticity_token'
#   input.value = token
#   form.appendChild(input)
# </script>

Reference

CSRF Protection Methods

Method Description Use Case
Synchronizer Token Server generates unique token per session, validates on requests Default Rails protection, stateful applications
Double-Submit Cookie Token stored in cookie, submitted in header for comparison Stateless APIs, microservices
SameSite Cookies Browser restricts cookie sending to same-site requests Modern browsers, primary domain only
Custom Headers Require custom header that simple forms cannot set API endpoints, JavaScript-heavy apps
Origin Validation Verify Origin/Referer header matches expected domain Defense in depth, supplement to token validation

Rails Protection Strategies

Strategy Behavior When to Use
:exception Raise ActionController::InvalidAuthenticityToken Development, debugging
:null_session Clear session on validation failure Production, continue with empty session
:reset_session Reset entire session on validation failure High-security applications

HTTP Methods and CSRF Risk

Method Should Modify State CSRF Risk Protection Required
GET No Low (if used correctly) No, but validate sensitive queries
POST Yes High Yes, CSRF token required
PUT Yes High Yes, CSRF token required
PATCH Yes High Yes, CSRF token required
DELETE Yes High Yes, CSRF token required
HEAD No None No
OPTIONS No None No

SameSite Cookie Values

Value Cross-Site Sending Protection Level Compatibility
Strict Never Highest May break legitimate flows
Lax Only safe methods (GET) High Good balance
None Always (requires Secure) Low Required for cross-site cookies

Rails Configuration Options

Option Default Purpose
per_form_csrf_tokens false Generate unique token per form
forgery_protection_origin_check false Validate Origin header
allow_forgery_protection true Enable/disable CSRF protection
default_protect_from_forgery false Protect all controllers by default

Token Validation Headers

Header Purpose Required
X-CSRF-Token Submit CSRF token in AJAX requests For state-changing AJAX
X-Requested-With Indicate AJAX request Optional, additional validation
Authorization JWT or OAuth token Alternative to CSRF for APIs
Origin Request origin domain Automatic, used for validation
Referer Previous page URL Automatic, fallback for validation

Common Rails Helper Methods

Method Purpose Example
form_authenticity_token Get current CSRF token form_authenticity_token
csrf_meta_tags Render meta tags with token <%= csrf_meta_tags %>
protect_from_forgery Enable CSRF protection protect_from_forgery with: :exception
skip_before_action Disable protection for action skip_before_action :verify_authenticity_token
verify_authenticity_token Manually trigger validation verify_authenticity_token
verified_request? Check if request passes validation verified_request?

Attack Indicators

Indicator Possible CSRF Attack Investigation Action
Missing/invalid CSRF token Failed CSRF attempt Log IP, user agent, monitor for patterns
Origin header mismatch Cross-origin request Verify legitimate cross-origin needs
Unusual request timing Automated attack Check for rapid requests, same source
Unexpected state changes Successful CSRF Audit recent changes, check logs
User reports unauthorized actions Successful attack Review user's recent activity, reset session