require 'sinatra/base'
require 'rack/protection'
class TransferApp < Sinatra::Base
enable :sessions
set :session_secret, ENV.fetch('SESSION_SECRET', 'a' * 64)
# Only guards unsafe verbs (POST/PUT/PATCH/DELETE); GETs pass through.
use Rack::Protection::AuthenticityToken
helpers do
def csrf_token
env['rack.session'][:csrf]
end
def csrf_tag
%(<input type="hidden" name="authenticity_token" value="#{csrf_token}">)
end
end
get '/transfer' do
erb :transfer
end
post '/transfer' do
# Reaches here only if the middleware validated the token.
amount = params[:amount].to_i
to = params[:to].to_s
"Transferred #{amount} to #{to}"
end
run! if app_file == $0
end
<h1>Send money</h1>
<form action="/transfer" method="post">
<%= csrf_tag %>
<label>
Recipient
<input type="text" name="to" required>
</label>
<label>
Amount
<input type="number" name="amount" min="1" required>
</label>
<button type="submit">Transfer</button>
</form>
require 'rack/test'
require 'rspec'
require_relative '../app'
RSpec.describe TransferApp do
include Rack::Test::Methods
def app
TransferApp
end
it 'rejects a POST without a CSRF token' do
post '/transfer', to: 'mallory', amount: '1000'
expect(last_response.status).to eq(403)
end
it 'accepts a POST carrying the session token' do
get '/transfer'
token = last_request.env['rack.session'][:csrf]
post '/transfer', to: 'alice', amount: '50', authenticity_token: token
expect(last_response).to be_ok
expect(last_response.body).to include('Transferred 50 to alice')
end
end
This snippet shows how a classic Sinatra application defends state-changing form submissions against cross-site request forgery (CSRF) using Rack::Protection::AuthenticityToken. CSRF is an attack where a malicious page tricks a logged-in user's browser into submitting a request to another site using the ambient session cookie. The standard defense is the synchronizer token pattern: the server embeds an unpredictable per-session token in every form, and rejects any unsafe request whose token is missing or wrong.
In app.rb, session support is enabled with enable :sessions and a session_secret, which is a hard prerequisite — the token is derived from the session, so without a signed session cookie there is nothing to bind the token to. The app deliberately activates Rack::Protection::AuthenticityToken on its own rather than pulling in the whole Rack::Protection bundle, keeping the middleware stack explicit. A helpers block exposes csrf_token, which reads the value the middleware stashes in env['rack.session'][:csrf], and csrf_tag, which renders a ready-to-use hidden input. The POST /transfer route is the protected action; by the time its block runs, the middleware has already validated the incoming token, so the handler can assume the request is legitimate.
A subtle but important detail is that Rack::Protection::AuthenticityToken only guards unsafe HTTP verbs — POST, PUT, PATCH, DELETE. GET requests pass through untouched, which is correct because safe methods must never mutate state. This is also why the transfer must be a POST and not a GET.
The transfer.erb view demonstrates the client side: csrf_tag injects the hidden field, so an ordinary browser form submission carries the token automatically. Any forged cross-origin form lacks the value and is rejected with a 403 before the route ever executes.
The spec/csrf_spec.rb tests lock in both halves of the contract — a POST without a token is refused, and a POST echoing the session's token succeeds. A common pitfall worth noting is AJAX: fetch/XHR requests must send the token via a header or body field explicitly, since there is no form to carry it. Reaching for this pattern is appropriate for any server-rendered Sinatra app that mutates data behind a session.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["form"]
static values = { delay: { type: Number, default: 250 } }
Debounced live search with Stimulus + Turbo Streams
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
Share this code
Here's the card — post it anywhere.