Clickjacking in Flask with Bearer Tokens
Clickjacking in Flask with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack where an attacker tricks a user into interacting with a hidden or disguised element inside an invisible or transparent iframe. In Flask applications that use Bearer Tokens for authentication, combining token-based auth with missing anti-clickjacking protections can expose sensitive endpoints to exploitation. When a Flask route relies on an Authorization header containing a Bearer Token and lacks proper framing defenses, an attacker can embed the authenticated app in an iframe and lure the user into performing actions while the request carries the user’s token automatically via cookies or JavaScript.
Consider a Flask route that accepts sensitive operations (e.g., changing email or initiating a transfer) and validates only the Bearer Token in the Authorization header:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/transfer", methods=["POST"])
def transfer():
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
return jsonify({"error": "unauthorized"}), 401
token = auth.split(" ")[1]
# validate token, perform transfer
return jsonify({"status": "ok"})
If this route is served to an authenticated user in a page controlled by an attacker (e.g., via a malicious site), and the user’s browser includes session cookies or the token is accessible via JavaScript, the request will be executed with the user’s privileges. Even when Bearer Tokens are used, if the application does not enforce same-origin framing policies, an attacker can induce state-changing requests without the user’s informed consent. This is particularly risky when tokens are stored in JavaScript-accessible storage or when the app does not set appropriate HTTP response headers to prevent embedding.
The risk is compounded when the Flask app also exposes an unauthenticated endpoint or a page that loads third-party content without X-Frame-Options or Content-Security-Policy with frame-ancestors. In such cases, the combination of a valid Bearer Token in an authenticated request and a lack of frame restrictions creates a clear clickjacking surface. The scanner checks for missing frame-ancestors policies and flags endpoints that perform sensitive actions without anti-clickjacking headers, aligning findings with OWASP API Top 10 and related framework guidance.
Bearer Tokens-Specific Remediation in Flask — concrete code fixes
To mitigate clickjacking risks in Flask applications that use Bearer Tokens, you should enforce strict framing rules and ensure tokens are not unnecessarily exposed to client-side JavaScript. Below are concrete, safe code examples that demonstrate recommended defenses.
1) Set HTTP headers to prevent framing on all responses. Use X-Frame-Options and a strict Content Security Policy with frame-ancestors:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.after_request
def set_frame_options(response):
response.headers["X-Frame-Options"] = "DENY"
response.headers["Content-Security-Policy"] = "frame-ancestors 'none';"
return response
@app.route("/transfer", methods=["POST"])
def transfer():
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
return jsonify({"error": "unauthorized"}), 401
token = auth.split(" ")[1]
# validate token, perform transfer
return jsonify({"status": "ok"})
2) For APIs consumed by your own frontend, restrict framing to trusted origins instead of denying all frames:
from flask import Flask, request, make_response
app = Flask(__name__)
@app.after_request
def set_frame_options(response):
response.headers["X-Frame-Options"] = "ALLOW-FROM https://trusted.example.com"
# Note: ALLOW-FROM is not supported in all browsers; prefer CSP frame-ancestors
response.headers["Content-Security-Policy"] = "frame-ancestors https://trusted.example.com;"
return response
3) Avoid exposing Bearer Tokens to JavaScript when possible. Do not store tokens in localStorage or render them in pages that may be embedded. If you must pass the token to client-side code, use short-lived tokens and ensure pages are not embeddable:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/api/data")
def get_data():
auth = request.headers.get("Authorization", "")
if not auth or not auth.startswith("Bearer "):
return jsonify({"error": "unauthorized"}), 401
token = auth.split(" ")[1]
# Validate token with your auth provider, then return data
return jsonify({"data": "sensitive"})
4) Combine these headers with server-side validation and same-site cookie attributes if cookies are also used. Always validate the Bearer Token on every request and avoid relying solely on the token’s presence in headers without proper framing controls. These measures reduce the attack surface for clickjacking against Bearer Token-protected Flask endpoints.