HIGH cross site request forgerysinatrabearer tokens

Cross Site Request Forgery in Sinatra with Bearer Tokens

Cross Site Request Forgery in Sinatra with Bearer Tokens

Cross Site Request Forgery (CSRF) in Sinatra when Bearer Tokens are used involves a mismatch between token-based authentication and the browser’s cookie-based session handling. Bearer tokens are typically stored in client-side JavaScript or browser storage and sent via the Authorization header. When a browser automatically includes cookies (e.g., session or CSRF tokens) but the backend relies solely on the Authorization header, an attacker can craft a form on a malicious site that triggers state-changing requests. The browser will include cookies automatically, but the missing or incorrect CSRF protection on the API endpoint can allow the request to succeed if the token is valid and not properly validated against the origin.

In Sinatra, a common pattern is to authenticate requests using a before filter that reads the Authorization header and validates the Bearer token. However, if the application does not verify the request origin or enforce explicit CSRF defenses for state-changing methods (POST, PUT, PATCH, DELETE), an attacker can trick a logged-in user’s browser into submitting a request like:

POST /api/transfer HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiMSJ9.xxxxx
Content-Type: application/x-www-form-urlencoded

amount=1000&to=attacker

If the Sinatra route trusts the Authorization header and does not validate the Origin or Referer headers, or does not require an explicit anti-CSRF token in the request body or header, the server may process the transfer. This occurs because the browser sends cookies (if any are present) and the Authorization header together, and the server conflates a valid token with a legitimate user intent. The risk is elevated when tokens are long-lived or when the API serves both web and non-web clients without clear separation.

CSRF against Bearer-token APIs is not automatic; it requires the attacker to know or guess a valid token or to leverage a token that is accidentally exposed to the browser. If tokens are stored in HttpOnly, Secure cookies and not accessible to JavaScript, direct CSRF via injected forms is less likely. However, if the token is exposed to JavaScript (e.g., stored in localStorage and added to Authorization headers manually), an XSS flaw can complement CSRF to achieve a more severe impact. Therefore, treating Bearer-token endpoints as CSRF-protected by default is unsafe in Sinatra without explicit mitigations.

Bearer Tokens-Specific Remediation in Sinatra

To secure Sinatra endpoints that use Bearer tokens, implement explicit CSRF protections tailored to token-based flows. The goal is to ensure that requests with a Bearer token are not inadvertently accepted from untrusted origins. Below are concrete, actionable fixes with code examples.

  • Validate the Origin and Referer headers for state-changing requests. Reject requests where the origin does not match your trusted domain:
require 'sinatra'
require 'json'

helpers do
  def authorized?(token)
    # Replace with your token validation logic
    token == 'VALID_TOKEN_EXAMPLE'
  end

  def allowed_origin?
    origin = request.env['HTTP_ORIGIN']
    referer = request.env['HTTP_REFERER']
    trusted = 'https://yourtrusted.com'
    origin == trusted || referer&.start_with?(trusted)
  end
end

before do
  # Skip CSRF checks for safe methods
  return if ['GET', 'HEAD', 'OPTIONS'].include?(request.request_method)

  auth = request.env['HTTP_AUTHORIZATION']
  token = auth&.split&.last
  halt 401, { error: 'Unauthorized' }.to_json unless token && authorized?(token)
  halt 403, { error: 'Forbidden: Invalid origin' }.to_json unless allowed_origin?
end

post '/api/transfer' do
  content_type :json
  { status: 'ok' }.to_json
end
  • Use per-route or per-method CSRF tokens when the API serves browser-originated clients. Require a custom header (e.g., X-CSRF-Token) that matches a server-side value:
enable :sessions

get '/api/csrf_token' do
  session[:csrf_token] ||= SecureRandom.hex(32)
  { csrf_token: session[:csrf_token] }.to_json
end

post '/api/action' do
  auth = request.env['HTTP_AUTHORIZATION']
  token = auth&split&.last
  halt 401, { error: 'Unauthorized' }.to_json unless token && authorized?(token)

  expected = session[:csrf_token]
  actual = request.env['HTTP_X_CSRF_TOKEN']
  halt 403, { error: 'Invalid CSRF token' }.to_json unless expected && secure_compare(expected, actual)

  content_type :json
  { status: 'success' }.to_json
end

def secure_compare(a, b)
  return false if a.nil? || b.nil? || a.bytesize != b.bytesize
  l = a.unpack 'H*'
  r = b.unpack 'H*'
  return false unless l && r && l.first.bytesize == r.first.bytesize
  # Constant-time comparison to avoid timing attacks
  (l.first ^ r.first).hexzero?
end
  • For APIs consumed only by non-browser clients or mobile apps, disable CSRF protections selectively and document this clearly. Use a configuration flag to skip origin checks when necessary, but ensure tokens remain in the Authorization header and are not stored in cookies without HttpOnly and Secure flags.
configure :production do
  set :csrf_protected, true
end

before do
  return if ['GET', 'HEAD', 'OPTIONS'].include?(request.request_method)
  # Only enforce CSRF for web-originated traffic
  if settings.csrf_protected && !allowed_origin?
    halt 403, { error: 'CSRF protection failed' }.to_json
  end
  # Bearer token validation as above
end

Frequently Asked Questions

Can a Bearer token in an Authorization header still be vulnerable to CSRF?
Yes, if the Sinatra backend does not validate the request origin or require an anti-CSRF token for state-changing methods. Browsers automatically send cookies, and if the server conflates a valid Authorization header with legitimate intent, CSRF can occur.
Is storing Bearer tokens in cookies safe against CSRF in Sinatra?
Storing tokens in HttpOnly, Secure cookies reduces exposure to JavaScript-based theft but does not prevent CSRF. You should still validate Origin/Referer headers or use per-request CSRF tokens for state-changing requests in Sinatra.