HIGH broken access controlflaskbearer tokens

Broken Access Control in Flask with Bearer Tokens

Broken Access Control in Flask with Bearer Tokens — how this specific combination creates or exposes the vulnerability

Broken Access Control is a common API security risk where authorization checks are missing or inconsistent, allowing one user to access or modify resources belonging to another. In Flask APIs that rely on Bearer Tokens for authentication, this risk increases when token validation is performed but authorization decisions are not enforced on every request. A typical pattern is to decode and verify the token’s validity (e.g., signature and expiration) but then skip checking the token’s scopes or the resource’s ownership before performing a sensitive operation.

Consider a Flask endpoint that retrieves or modifies user data using a user ID derived from the token claims without confirming that the requesting user is allowed to access that specific user ID. If the endpoint trusts the decoded payload without additional checks, an attacker who obtains or guesses another user’s ID can directly access or manipulate data. This is a classic BOLA/IDOR pattern, and it maps to the Broken Access Control category in the OWASP API Top 10.

Flask does not enforce authorization by default, so developers must explicitly add logic to compare the authenticated subject (often the user identifier in the token) with the target resource. For example, an endpoint like /users//profile that uses a Bearer Token containing sub and roles must ensure the authenticated sub matches the requested user_id and that the token’s scopes or roles permit the action. Missing role- or scope-based checks, missing object ownership validation, and permissive CORS or indirect object references further expand the attack surface. Even when an OpenAPI spec describes required security schemes, runtime enforcement must align with the spec to prevent unauthorized access.

Real-world attack patterns include IDOR via predictable numeric identifiers, privilege escalation through tampered role claims, and horizontal privilege abuse when token validation does not align with authorization middleware. The combination of Flask routes that accept user-controlled identifiers and Bearer Tokens that are not tied to fine-grained permissions creates a scenario where authentication does not guarantee safe authorization. This misalignment is what middleBrick’s BOLA/IDOR and Authentication checks aim to detect, highlighting missing or incorrect authorization logic even when tokens appear valid.

Bearer Tokens-Specific Remediation in Flask — concrete code fixes

To fix Broken Access Control with Bearer Tokens in Flask, enforce strict ownership checks and scope/role validation on every request. Below are concrete, working code examples that demonstrate a secure approach using PyJWT for token decoding and explicit authorization logic.

Example: Secure token validation and ownership check

First, validate the Bearer Token and ensure the endpoint enforces that the authenticated subject matches the requested resource identifier.

from flask import Flask, request, jsonify, g
import jwt
import functools

app = Flask(__name__)
SECRET_KEY = 'your-secret-key'
ALGORITHM = 'HS256'

def get_token_auth_header():
    auth = request.headers.get('Authorization', '')
    if not auth.lower().startswith('bearer '):
        return None
    return auth.split(' ')[1]

def decode_token(token):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

def token_required(f):
    @functools.wraps(f)
    def decorated(*args, **kwargs):
        token = get_token_auth_header()
        if not token:
            return jsonify({'error': 'Authorization header missing or invalid'}), 401
        payload = decode_token(token)
        if not payload:
            return jsonify({'error': 'Invalid or expired token'}), 401
        g.user = payload  # store user claims on the request context
        return f(*args, **kwargs)
    return decorated

def owns_user_resource(f):
    @functools.wraps(f)
    def decorated(*args, **kwargs):
        # Ensure the requesting user matches the resource they are accessing
        requested_user_id = kwargs.get('user_id')
        if not requested_user_id:
            return jsonify({'error': 'Missing user identifier'}), 400
        if str(g.user.get('sub')) != requested_user_id:
            return jsonify({'error': 'Forbidden: you can only access your own profile'}), 403
        return f(*args, **kwargs)
    return decorated

@app.route('/users//profile', methods=['GET'])
@token_required
@owns_user_resource
def get_user_profile(user_id):
    # At this point, g.user is guaranteed to exist and the user_id matches g.user['sub']
    return jsonify({'user_id': user_id, 'profile': 'safe-profile-data'})

if __name__ == '__main__':
    app.run(debug=False)

Role- and scope-based authorization example

When endpoints require specific scopes or roles, validate those claims in addition to ownership checks. This prevents privilege escalation via tampered tokens.

def scope_required(required_scope):
    def decorator(f):
        @functools.wraps(f)
        def wrapped(*args, **kwargs):
            token = get_token_auth_header()
            if not token:
                return jsonify({'error': 'Missing token'}), 401
            payload = decode_token(token)
            if not payload:
                return jsonify({'error': 'Invalid token'}), 401
            scopes = payload.get('scopes', [])
            if required_scope not in scopes:
                return jsonify({'error': 'Insufficient scope'}), 403
            g.user = payload
            return f(*args, **kwargs)
        return wrapped
    return decorator

@app.route('/admin/settings', methods=['GET'])
@token_required
@scope_required('admin:settings')
def get_admin_settings():
    return jsonify({'settings': 'admin-only-data'})

# Example token payload expectations:
# {
#   "sub": "12345",
#   "roles": ["user"],
#   "scopes": ["read:profile", "admin:settings"]
# }

Frequently Asked Questions

What is BOLA/IDOR and why does it matter with Bearer Tokens?
BOLA (Broken Object Level Authorization) / IDOR occurs when an API exposes endpoints that lack proper authorization checks, allowing attackers to access or modify other users’ resources by manipulating identifiers (e.g., user_id). With Bearer Tokens, authentication verifies identity but does not automatically enforce authorization; without explicit checks that the authenticated subject matches the requested resource, BOLA/IDOR vulnerabilities arise even when tokens are valid.
Can simply validating a Bearer Token prevent Broken Access Control?
No. Validating a Bearer Token confirms identity and token integrity, but does not enforce authorization. You must add explicit logic to ensure the authenticated subject is allowed to perform the requested action on the target resource, including scope/role checks and ownership comparisons, to prevent Broken Access Control.