Clickjacking in Sinatra with Bearer Tokens
Clickjacking in Sinatra with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side attack where an attacker tricks a user into interacting with a hidden or disguised UI element inside an iframe. In Sinatra applications that use Bearer Tokens for authentication, embedding unprotected resources inside frames can expose both the token and the end user to exploitation. When a Sinatra app sets an Authorization header with a Bearer Token but fails to enforce frame-embedding restrictions, an attacker can lure a victim to a malicious page that loads the app’s dashboard or admin panel inside an invisible iframe. Because the request includes a valid token, the embedded view may render as expected in the attacker’s page, making the token context and UI actions susceptible to manipulation via CSS and JavaScript overlays.
Even though Bearer Tokens are transmitted in headers rather than cookies, they can still be leveraged within an active session if the browser automatically includes credentials for the target origin. If the Sinatra app relies solely on token presence in the Authorization header and does not set appropriate anti-framing controls, the token’s associated UI becomes clickjackable. An attacker can craft a page that overlays interactive elements—such as buttons or forms—on top of the framed content, tricking the user into performing actions like changing settings or approving transactions while believing they are interacting with the attacker’s page. The risk is elevated when the app does not validate the Origin or Referer headers and does not enforce strict Content Security Policy (CSP) frame-ancestors rules.
Middleware that parses Authorization headers and sets them on the Rack environment can inadvertently create a false sense of security. Token-based authentication does not inherently prevent framing; developers must explicitly instruct the browser not to allow the response to be embedded. Without a restrictive X-Frame-Options header or a modern CSP frame-ancestors directive, responses containing sensitive views are embeddable. This becomes a significant concern when tokens are long-lived or when the app serves pages that include sensitive operations inside iframes from other origins. Proper mitigation requires both server-side header enforcement and client-side design considerations to isolate sensitive actions from external contexts.
Bearer Tokens-Specific Remediation in Sinatra — concrete code fixes
To defend against clickjacking in Sinatra when using Bearer Tokens, you should enforce strict framing rules by setting HTTP headers and, where necessary, applying CSP directives. These controls are independent of the authentication mechanism but are essential when rendering pages that include sensitive actions. The following examples demonstrate how to configure a Sinatra app to prevent embedding while still supporting token-based flows.
Set X-Frame-Options
The simplest and widely supported approach is to set the X-Frame-Options header. Use DENY for endpoints that should never be framed, or SAMEORIGIN if you need to allow framing within your own domain. In Sinatra, this can be applied globally or per route.
require 'sinatra'
# Global protection for all responses
before do
response.headers['X-Frame-Options'] = 'DENY'
end
get '/dashboard' do
# Your token validation logic here
# Ensure the Authorization header contains a valid Bearer Token
auth = request.env['HTTP_AUTHORIZATION']
halt 401, { error: 'Unauthorized' }.to_json unless auth&.start_with?('Bearer ')
# Render sensitive UI
erb :dashboard
end
Content Security Policy frame-ancestors
For modern browsers, the Content Security Policy (CSP) frame-ancestors directive provides finer control. You can allow specific origins or restrict framing more tightly than X-Frame-Options allows. Combine CSP with X-Frame-Options for defense-in-depth, but ensure the policies do not conflict.
require 'sinatra'
before do
# Prevent any site from framing this response
response.headers['Content-Security-Policy'] = "frame-ancestors 'none'"
# Or allow only same-origin framing:
# response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
end
post '/api/transfer' do
auth = request.env['HTTP_AUTHORIZATION']
halt 401, { error: 'Invalid token' }.to_json unless auth&;.match?(/\ABeer [A-Za-z0-9-_]+\z/)
# Perform transfer logic
{ status: 'ok' }.to_json
end
Token-specific framing considerations
When your routes conditionally include sensitive operations based on Bearer Token scopes, apply framing protections consistently across all authenticated views. Even if a route returns JSON, ensure that responses intended for browser rendering are protected. Avoid embedding routes that perform state-changing operations inside iframes, regardless of token validity. Use CSP reports to monitor violations and refine your policy in production.
# Example of per-route strict framing for a sensitive endpoint
get '/admin/delete-user' do
auth = request.env['HTTP_AUTHORIZATION']
halt 403, { error: 'Forbidden' }.to_json unless auth&.start_with?('Bearer ')
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Content-Security-Policy'] = "frame-ancestors 'none'"
# Proceed with admin logic
{ deleted: true }.to_json
end