HIGH api key exposureflasksession cookies

Api Key Exposure in Flask with Session Cookies

Api Key Exposure in Flask with Session Cookies — how this specific combination creates or exposes the vulnerability

Storing API keys in Flask session cookies can inadvertently expose secrets when session handling is misconfigured. Flask’s default session implementation signs cookies using SECRET_KEY and stores data in the cookie payload (client-side signed cookies). If an API key is placed into session and the app uses an insecure configuration, the key can be exposed through multiple vectors.

One common pattern is assigning an external service key directly to the session:

from flask import Flask, session

app = Flask(__name__)
app.secret_key = 'dev-secret-not-safe'

@app.route('/set_key')
def set_key():
    session['api_key'] = 'sk_live_abc123xyz'
    return 'API key stored in session'

With default settings, this stores the API key in a base64-encoded, signed cookie. While the signature prevents tampering, the data is not encrypted and is readable by anyone who can extract the cookie. If an attacker obtains the cookie (via XSS, insecure logging, or browser theft), the API key is compromised.

Additionally, if SESSION_COOKIE_SECURE is not set to True, the cookie may be transmitted over HTTP in development or misconfigured environments, enabling network eavesdropping. Similarly, without SESSION_COOKIE_HTTPONLY, JavaScript can read the cookie, increasing exposure risk via XSS. Flask does not encrypt session contents by default; signing ensures integrity but not confidentiality. Therefore, placing an API key into session effectively stores the secret on the client side, which violates the principle of never exposing long-lived credentials to the browser.

Other subtle risks arise when session data is serialized using JSON or itsdangerous structures that include metadata. If the application logs session contents (for debugging) or if browser history is shared across users, the key can be inadvertently persisted or leaked. Cross-site request forgery (CSRF) or session fixation can also lead to session hijacking, indirectly exposing the API key. These patterns highlight that the combination of Flask’s cookie-based sessions and direct API key storage creates a clear path for credential exposure.

Session Cookies-Specific Remediation in Flask — concrete code fixes

Remediation focuses on avoiding storing API keys in session altogether. If you must associate keys with a user session, keep them on the server side and reference them with a non-sensitive identifier stored in the cookie.

1. Do not store API keys in Flask session. Instead, store keys server-side (e.g., in a secure cache or database) and store only a reference in the session:

from flask import Flask, session
import secrets

app = Flask(__name__)
app.secret_key = 'super-secret-key-change-in-production'
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Lax',
)

# Server-side storage example (in-memory dict for illustration; use Redis in production)
server_side_store = {}

@app.route('/set_key')
def set_key():
    key_id = secrets.token_hex(16)
    server_side_store[key_id] = 'sk_live_abc123xyz'
    session['key_id'] = key_id
    return 'Key reference stored securely'

@app.route('/use_key')
def use_key():
    key_id = session.get('key_id')
    if key_id and key_id in server_side_store:
        api_key = server_side_store[key_id]
        return 'Using server-side key'
    return 'No key available', 400

This approach ensures the API key never leaves the server. The session only holds a random, non-sensitive reference (key_id), and the actual secret remains in a protected backend store.

2. Enforce secure cookie attributes to prevent transmission over insecure channels and mitigate XSS-based theft:

from flask import Flask

app = Flask(__name__)
app.config.update(
    SECRET_KEY='change-this-to-a-long-random-value-in-production',
    SESSION_COOKIE_SECURE=True,       # Send cookie only over HTTPS
    SESSION_COOKIE_HTTPONLY=True,     # Prevent JavaScript access
    SESSION_COOKIE_SAMESITE='Lax',    # Mitigate CSRF
    PERMANENT_SESSION_LIFETIME=1800,  # 30 minutes timeout
)

With these settings, the session cookie is not sent over HTTP, is inaccessible to scripts, and is bound to a same-site policy, reducing the likelihood of exposure through network sniffing or client-side attacks.

3. Rotate and revoke keys. Implement mechanisms to rotate API keys and tie them to user sessions with expiration. Combine with Flask’s session invalidation on logout to clean up references:

@app.route('/logout')
def logout():
    session.clear()
    return 'Logged out'

By removing the key reference on logout and using short-lived server-side keys, you limit the window of exposure if a session is compromised.

Frequently Asked Questions

Is it safe to store short-lived API keys in Flask session if I use HTTPS?
Even with HTTPS, storing API keys in Flask session is not recommended because the key resides in the client-side cookie. If the cookie is leaked (via XSS, browser sharing, or logs), the key is exposed. Prefer server-side storage with a session reference.
How can I ensure session cookies are not persisted across browser sessions?
Set SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, and use session.permanent = False or configure a short PERMANENT_SESSION_LIFETIME so cookies are session-scoped and cleared when the browser closes.