Class: Sinatra::Inertia::CSRFMiddleware

Inherits:
Object
  • Object
show all
Defined in:
lib/sinatra/inertia/csrf_middleware.rb

Overview

Rack middleware implementing the Inertia / Laravel “double-submit cookie” CSRF pattern that ‘@inertiajs/react` and `@inertiajs/vue3` honour out of the box.

Behaviour


  • On every request, ensure a token cookie named ‘XSRF-TOKEN` is present (generate + set on the response if missing).

  • For non-safe methods (POST / PUT / PATCH / DELETE), require the request to send ‘X-XSRF-TOKEN` whose value matches the cookie. Mismatch → `403 Forbidden`.

  • The cookie is not HttpOnly — Inertia’s client reads it from ‘document.cookie` and forwards it as `X-XSRF-TOKEN` automatically.

Caveats


Double-submit cookie is the standard Inertia/Laravel pattern but is weaker than synchronizer-token CSRF when an attacker has any script-injection foothold. Pair with:

* `SameSite=Lax` (default below) — the cookie won't ride
  cross-site form posts.
* Strict CSP / no XSS.
* Optionally, server-side session-bound tokens via
  `Rack::Protection::AuthenticityToken` instead.

Configuration


‘set :inertia_csrf_protection, false` disables this middleware. Use this only when the consumer ships its own CSRF defence. Safer defaults assume the gem is responsible.

Constant Summary collapse

'XSRF-TOKEN'
HEADER_KEY =
'HTTP_X_XSRF_TOKEN'
ENV_TOKEN_KEY =
'sinatra.inertia.csrf_token'
SAFE_METHODS =
%w[GET HEAD OPTIONS].freeze

Instance Method Summary collapse

Constructor Details

#initialize(app, same_site: :Lax) ⇒ CSRFMiddleware

Returns a new instance of CSRFMiddleware.



44
45
46
47
# File 'lib/sinatra/inertia/csrf_middleware.rb', line 44

def initialize(app, same_site: :Lax)
  @app = app
  @same_site = same_site
end

Instance Method Details

#call(env) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/sinatra/inertia/csrf_middleware.rb', line 49

def call(env)
  existing = read_cookie(env)
  token = existing || SecureRandom.urlsafe_base64(32)
  env[ENV_TOKEN_KEY] = token

  unless safe_method?(env)
    header = env[HEADER_KEY].to_s
    if existing.nil? || header.empty? || !secure_compare(header, existing)
      return forbidden('CSRF token mismatch (expected matching X-XSRF-TOKEN header to XSRF-TOKEN cookie)')
    end
  end

  status, headers, body = @app.call(env)
  unless existing == token
    set_cookie!(headers, token)
  end
  [status, headers, body]
end