Open Redirect in Flask with Api Keys
Open Redirect in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
An open redirect in a Flask application that uses API keys occurs when an endpoint accepts a user-supplied URL or redirect target and incorporates an API key in the flow without proper validation. For example, a common pattern is a callback or OAuth redirect where the client provides a next or redirect_uri parameter, and the server uses an API key to authenticate outgoing requests or to sign a redirect token. If the API key is embedded in the redirect response (e.g., as a query parameter or in a Location header) and the target URL is not strictly validated, an attacker can supply a malicious host, causing the application to redirect authenticated users or leak the key in Referer headers.
Consider a Flask route that accepts a redirect URL and an API key for downstream service authorization:
from flask import Flask, request, redirect, url_for, make_response
app = Flask(__name__)
@app.route('/callback')
def callback():
api_key = request.args.get('api_key')
redirect_to = request.args.get('next', '/') # user-controlled
# Danger: using the API key in a redirect or token that includes user input
token = f'key={api_key}'
return redirect(f'{redirect_to}?token={token}')
In this pattern, if redirect_to is not validated, an attacker can set next to a malicious domain (e.g., https://evil.com/?code=stolen). The API key may be exposed in the Referer header when the browser follows the redirect, or the application may be tricked into issuing a signed token or OAuth state that points to an attacker-controlled host. This violates the same-origin principle and can lead to phishing or session fixation, especially when the API key is used to authorize downstream calls that are chained to the redirect.
Another scenario involves API-key-gated admin endpoints that perform server-side redirects to third-party services (e.g., payment gateways). If the service URL is built from user input and the API key is logged or echoed in error messages, an attacker can craft a redirect that causes the server to follow an unintended path, potentially exfiltrating the key via logs or SSRF-adjacent behaviors. Because the API key is treated as a credential, exposing it through an open redirect significantly increases the impact, as the key can be reused across requests.
Api Keys-Specific Remediation in Flask — concrete code fixes
To remediate open redirect risks when API keys are involved, treat the API key as a sensitive credential that must never be exposed in redirects or URLs. Always validate and normalize redirect targets against an allowlist, and avoid echoing API keys in responses, headers, or logs. Below are concrete, secure patterns for Flask.
1. Validate redirect targets against an allowlist
Only permit redirects to known, trusted paths or domains. Do not rely on hostname checks alone; use URL parsing to enforce strict path and scheme rules.
from flask import Flask, request, redirect, url_for
from urllib.parse import urlparse, urljoin
app = Flask(__name__)
ALLOWED_REDIRECT_HOSTS = {'app.example.com', 'cdn.example.com'}
def is_safe_url(target):
ref = request.host_url
try:
parsed = urlparse(target)
if parsed.scheme not in {'http', 'https'}:
return False
# Ensure the target is relative or matches allowed hosts
if parsed.netloc and parsed.netloc not in ALLOWED_REDIRECT_HOSTS:
return False
# Use url_join to resolve relative URLs safely
safe_target = urljoin(request.host_url, target)
return safe_target.startswith(request.host_url)
except Exception:
return False
@app.route('/login')
def login():
api_key = request.args.get('api_key')
# Do NOT use api_key in redirect construction
next_url = request.args.get('next', '/')
if not is_safe_url(next_url):
next_url = '/'
# Store api_key securely server-side (e.g., session) rather than in redirect
return redirect(next_url)
2. Avoid embedding API keys in redirects or tokens
Never place API keys in query parameters, headers, or body of a redirect response. If a token is required for the client, generate a short-lived, server-side session or use signed cookies instead.
from flask import Flask, request, redirect, make_response
import secrets
app = Flask(__name__)
@app.route('/auth-callback')
def auth_callback():
api_key = request.args.get('api_key')
redirect_to = request.args.get('next', '/')
if not is_safe_url(redirect_to):
redirect_to = '/'
# Do not concatenate API key into redirect URL
session_id = secrets.token_urlsafe(16)
# Store mapping server-side (e.g., in memory cache) with limited TTL
# server_side_store[session_id] = api_key # managed securely
response = make_response(redirect(redirect_to))
response.set_cookie('session_id', session_id, httponly=True, samesite='Lax', secure=True)
return response
3. Sanitize inputs and use framework utilities
Always treat user input as untrusted. Use Werkzeug’s utilities to validate URLs and prefer relative paths. Log suspicious redirect attempts without exposing keys.
from werkzeug.urls import url_parse
@app.before_request
def validate_redirect_params():
if request.path == '/auth-callback':
next_url = request.args.get('next', '')
if next_url:
parsed = url_parse(next_url)
if parsed.host and parsed.host != request.host.split(':')[0]:
# Reject or sanitize
raise ValueError('Invalid redirect target')