Token Leakage in Flask with Basic Auth
Token Leakage in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability
Token leakage in a Flask application using HTTP Basic Authentication occurs when session tokens, API keys, or other bearer credentials are exposed through channels that bypass the intended protection of Basic Auth. Because Basic Auth sends a base64-encoded username:password pair in the Authorization header on every request, developers may mistakenly believe this is sufficient for overall API security and inadvertently expose secondary tokens via logs, error messages, URLs, or cross-service references.
In Flask, common leakage vectors include:
- Logging and error traces: If request headers are logged in full (e.g., via Flask’s built-in logger or a WSGI server), the Authorization header containing Basic credentials may be persisted. Tokens passed in custom headers (e.g.,
Authorization: Bearer <token>) may also be logged alongside Basic Auth headers, creating a combined exposure in log stores. - URL leakage and Referer headers: When a client using Basic Auth makes requests to external resources or follows redirects, URLs may include query parameters with tokens. Additionally, browsers and HTTP clients may send the Referer header containing the full request URL, leaking tokens to third parties.
- Cross-service credential confusion: A backend service validating Basic Auth might construct downstream requests by extracting a token from user input or session data and attaching it as a bearer token. If this token is derived from or identical to the Basic Auth password (or stored insecurely), compromise of one credential leads to compromise of both.
- Caching and proxy artifacts: Intermediate caches or proxies may store responses with Authorization headers if
Cache-Controland related headers are not explicitly set to prevent storage of authenticated responses. This can result in Basic credentials and any associated bearer tokens being retained beyond their intended scope.
An attacker who gains access to logs, network traffic, or cached responses can use leaked tokens to bypass intended authorization boundaries, escalate privileges (BFLA/Privilege Escalation), or perform actions on behalf of the compromised user. This is especially critical when tokens are long-lived or have broad scopes, as the combination of Basic Auth and token reuse amplifies the blast radius.
To detect such leakage patterns, scans can validate that sensitive credentials do not appear in response bodies, error messages, or logs, and that tokens are not derivable from Basic Auth credentials. The LLM/AI Security checks offered by tools like middleBrick can identify prompt or credential exposure scenarios that may arise from insecure integration patterns between authentication mechanisms and token handling.
Basic Auth-Specific Remediation in Flask — concrete code fixes
Remediation focuses on preventing credential and token leakage while maintaining the intended access control. Below are concrete, secure patterns for Flask applications using Basic Auth.
1. Avoid mixing Basic Auth with bearer tokens in the same request
Use one authentication scheme per request. If you must support both, route them to separate endpoints or services so that Basic Auth is isolated and tokens are never transmitted alongside it.
from flask import Flask, request, jsonify
import base64
app = Flask(__name__)
# Insecure: mixing schemes
@app.route("/insecure")
def insecure():
auth = request.authorization # Basic Auth
token = request.headers.get("Authorization", "").replace("Bearer ", "")
# Risk: both credentials may be logged or mishandled
return jsonify({"auth_user": auth.username if auth else None, "token_present": bool(token)})
# Secure: separate endpoints
@app.route("/basic")
def basic_only():
auth = request.authorization
if not auth or auth.username != "alice" or auth.password != "s3cret":
return jsonify({"error": "unauthorized"}), 401
return jsonify({"ok": True})
@app.route("/token")
def token_only():
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if not token or token != "abc123":
return jsonify({"error": "unauthorized"}), 401
return jsonify({"ok": True})
2. Prevent logging of Authorization headers
Configure your Flask app and any underlying WSGI server to exclude sensitive headers from access logs. If you must log for debugging, redact or hash credentials and tokens.
import logging
from flask import Flask, request
app = Flask(__name__)
class RedactingFilter(logging.Filter):
def filter(self, record):
# Redact Authorization headers in log messages
if hasattr(record, "message"):
record.msg = record.message.replace(request.headers.get("Authorization", ""), "[REDACTED]")
return True
handler = logging.StreamHandler()
handler.addFilter(RedactingFilter())
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
@app.route("/profile")
def profile():
# Do not log request headers directly
app.logger.info("Profile accessed")
return jsonify({"user": "alice"})
3. Use secure token handling and avoid token derivation from passwords
Generate independent, opaque tokens for session management. Store them securely on the server side or use short-lived signed JWTs. Never reuse the Basic Auth password as a token.
import secrets
from flask import Flask, request, jsonify, make_response
app = Flask(__name__)
SESSION_STORE = {}
@app.route("/login")
def login():
auth = request.authorization
if not auth or auth.username != "alice" or auth.password != "s3cret":
return jsonify({"error": "invalid credentials"}), 401
token = secrets.token_urlsafe(32)
SESSION_STORE[token] = auth.username
resp = make_response(jsonify({"token": token}))
resp.headers["Cache-Control"] = "no-store"
resp.headers["Pragma"] = "no-cache"
return resp
@app.route("/protected")
def protected():
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if token not in SESSION_STORE:
return jsonify({"error": "invalid token"}), 401
return jsonify({"user": SESSION_STORE[token]})
4. Control caching and Referer behavior
Set headers to prevent caching of authenticated responses and avoid leaking URLs with tokens in Referers.
@app.route("/data")
def data():
resp = make_response(jsonify({"sensitive": True}))
resp.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
resp.headers["Pragma"] = "no-cache"
resp.headers["Referrer-Policy"] = "no-referrer"
return resp
These measures reduce the risk that Basic Auth credentials or associated tokens appear in logs, caches, or network traces, aligning with findings from security scans that flag Token Leakage and related BOLA/IDOR and Data Exposure issues.