Kemal::Session::CSRF
Rails-compatible CSRF ("authenticity token") support, built on the shared session cookie.
This is a port of ActionController::RequestForgeryProtection (actionpack
8.1) with its SessionStore token strategy, so tokens are exchangeable in
both directions: a form rendered by Rails posts successfully to a Kemal
route and vice-versa. The pieces, all of which must stay byte-compatible:
- the real token lives in the session at
_csrf_token, encoded as unpadded urlsafe base64 of 32 random bytes (Rails'SecureRandom.urlsafe_base64(32)) - a token handed to a client is masked:
raw = HMAC-SHA256(real, identifier), thenurlsafe_base64(pad + (pad XOR raw))for a fresh 32-bytepad - the identifier is
"!real_csrf_token"for the global token, or"<action_path>#<method>"for a per-form token - verification unmasks and accepts the global token, the real token, or the per-form token for the current path+method
Only GET and HEAD skip verification — exactly like Rails, which does
not exempt OPTIONS/TRACE.
Constants
Identifier Rails HMACs to derive the global (non-per-form) token.
Header inspected in addition to the request parameter.
Request methods that skip verification, matching Rails' verified_request?.
Session key used by Rails' SessionStore token strategy.
Length of the raw (unmasked) token in bytes (Rails' AUTHENTICITY_TOKEN_LENGTH).
Class methods
Yields the candidate tokens carried by this request: the X-CSRF-Token
header plus the authenticity-token parameter from the query string, form
body or JSON body (Rails looks in the header and params).
Each source is touched only when the iteration reaches it, and they are
ordered by what they cost to read. That matters beyond speed: parsing a
multipart/form-data body spools every file part to disk, so a request
that matches on the header never pays for — or leaves behind — an upload
it didn't need to look at.
Rails' csrf_meta_tags, so Turbo/Rails UJS on a Kemal-rendered page picks
the token up and sends it as X-CSRF-Token.
Rails' normalize_action_path: strip query/fragment, drop a trailing
slash, and resolve a relative action against the current request path.
The encoded real token from the session, generated and persisted when
absent (Rails' real_csrf_token plus its commit_csrf_token write).
The candidate tokens carried by this request, as an array. Reads every
source; each_request_token is the short-circuiting form.
Drops the real token, so the next real_token call rotates it. Rails does
this on sign-in/sign-out (reset_csrf_token).
A masked token to hand to a client — the value Rails would render as
form_authenticity_token. Pass action and method to get a per-form
token (only honoured when config.csrf_per_form_tokens is on); action
may be relative, in which case request_path is used to resolve it.
Rails' valid_request_origin?: an absent Origin is accepted (some user
agents omit it), otherwise it must equal this request's base URL. Rails
raises on a literal "null" origin; here it simply fails the check.
Whether token is a valid authenticity token for session. path and method are the current request's, needed to accept per-form tokens.
Whether this request passes CSRF verification: safe methods always do,
anything else needs a same-origin request carrying a valid token in the
X-CSRF-Token header or the authenticity-token parameter.
Like verified_request?, but raises InvalidAuthenticityToken on failure
(Rails' protect_from_forgery with: :exception).