MEDIUM clickjackingrailsjwt tokens

Clickjacking in Rails with Jwt Tokens

Clickjacking in Rails with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Clickjacking is a client-side attack that tricks a user into interacting with a hidden or disguised UI element inside an iframe. When a Rails application uses JWT tokens for authentication but relies solely on cookie-based session handling for rendering frames, the combination can expose the application to clickjacking. A common pattern is to store a JWT in an HttpOnly cookie and use server-side view helpers to decide whether to render frames or embedded content. If these views do not explicitly prevent framing, an attacker can load the authenticated page inside an invisible iframe and overlay interactive controls, leading to unauthorized actions while the JWT is still valid.

Consider a Rails controller that authenticates via a JWT in a cookie and renders a page with sensitive actions such as changing an email or updating financial settings:

class DashboardController < ApplicationController
  before_action :authenticate_user_from_jwt

  def show
    # Renders a page with buttons that perform state-changing operations
  end

  def change_email
    # Performs email update based on params
  end

  private

  def authenticate_user_from_jwt
    token = cookies.signed[:jwt]
    # Decode and validate token; set current_user or render 401
  end
end

If the corresponding view does not set a framing policy, an attacker can craft a page like this:

<!----> Attacker-controlled page
<iframe src="https://app.example.com/dashboard/change_email" style="opacity:0;position:absolute"></iframe>
<button onclick="document.querySelector('iframe').contentDocument.querySelector('form button').click()">Win a prize!</button>

When the victim is logged in and has a valid JWT in a cookie, the hidden iframe submits the change_email action with the user’s authentication context. Rails does not automatically protect against this because the JWT validates identity but does not instruct the browser to block framing. The vulnerability is not in the JWT itself but in the missing anti-framing controls in Rails views and response headers.

Additionally, if the Rails app embeds third-party widgets or iframes and those frames include UI that can trigger authenticated actions, the risk increases. The JWT ensures the request is authenticated, but without proper X-Frame-Options or Content-Security-Policy frame-ancestors directives, the browser will honor the request and render the page inside the attacker’s frame. This demonstrates why authentication and framing policies must be addressed independently.

Jwt Tokens-Specific Remediation in Rails — concrete code fixes

To mitigate clickjacking in a Rails app using JWT tokens, combine secure token handling with HTTP headers and view-level framing controls. JWTs should not be placed in cookies that are automatically sent with cross-site requests unless SameSite and Secure attributes are enforced. Prefer Authorization: Bearer handling for API requests and avoid relying solely on cookies for sensitive actions that may be embedded in third-party frames.

Set X-Frame-Options and Content-Security-Policy headers to prevent framing of authenticated pages:

# config/initializers/frame_protection.rb
Rails.application.config.action_dispatch.default_headers = {
  'X-Frame-Options' => 'DENY',
  'Content-Security-Policy' => "frame-ancestors 'none'"
}

For pages that must be embedded in a controlled context (e.g., a dashboard widget), use a granular CSP instead of a global DENY:

#' Content-Security-Policy: frame-ancestors 'self' https://trusted.example.com

When handling JWTs in cookies, enforce SameSite and Secure attributes to reduce cross-site leakage:

# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
  key: '_app_session',
  same_site: :lax,
  secure: Rails.env.production?

If you choose to store the JWT in localStorage or another client-side mechanism, ensure your Rails views do not inadvertently expose the token via JavaScript in unsafe ways, and always use CSRF protection for state-changing requests that originate from browser contexts. For API-style endpoints consumed by JavaScript, validate the Authorization header and do not rely on cookie-based authentication alone.

Example of a controller that verifies a JWT from the Authorization header and safely handles sensitive actions:

class Api::V1::ProfilesController < ApplicationController
  before_action :authenticate_user_from_bearer_token

  def update_email
    # Only reachable with a valid bearer JWT
    current_user.update!(email_params)
    head :no_content
  end

  private

  def authenticate_user_from_bearer_token
    token = request.headers['Authorization']&.split(' ')&.last
    return head :unauthorized unless token

    # Decode and verify; set current_user or render 401
  end

  def email_params
    params.require(:email).permit(:address)
  end
end

Combine these practices to ensure that even if a page can be framed, authenticated actions are protected by strict token handling and framing policies. Regularly review CSP reports and test frame embedding scenarios to confirm protections are effective.

Frequently Asked Questions

Does placing a JWT in a cookie automatically protect against clickjacking in Rails?
No. A JWT in a cookie only provides authentication; it does not prevent the page from being embedded in an iframe. You must explicitly set X-Frame-Options or Content-Security-Policy frame-ancestors headers to prevent framing.
Should I store JWTs in cookies or in localStorage to prevent clickjacking?
Neither storage method prevents clickjacking by itself. Use HttpOnly, Secure, SameSite cookies for browser-based apps, or Bearer tokens in the Authorization header for API clients, and always apply anti-framing headers to authenticated pages.