Graphql Introspection in Flask with Bearer Tokens
Graphql Introspection in Flask with Bearer Tokens — how this specific combination creates or exposes the vulnerability
GraphQL introspection is a feature that allows clients to query the schema, types, and operations of a GraphQL API. When introspection is enabled in a Flask-based GraphQL endpoint and the API relies on Bearer Tokens for authorization, a common misconfiguration can expose both the schema and authorization logic to unauthenticated or insufficiently authenticated attackers.
In Flask, developers often enable introspection to support development tools like GraphiQL or Apollo Studio. However, if introspection is left accessible without proper authorization checks, any client that can reach the endpoint—regardless of whether a valid Bearer Token is provided—can retrieve the full schema. This becomes a security risk when the GraphQL server is protected only by Bearer Tokens that are not consistently validated before introspection is allowed.
The vulnerability occurs when the Flask route handling GraphQL requests does not distinguish between queries that require authentication and introspection queries that do not. An attacker can send an introspection query over HTTP without including a Bearer Token or with an invalid token, and the server may still return the schema. This reveals details such as query names, input types, and field structures, which can aid in crafting further attacks, such as IDOR or BOLA, especially when combined with other weaknesses in the API.
Additionally, if the Bearer Token validation logic is implemented at a layer below the GraphQL execution layer, introspection requests may bypass token checks entirely. For example, if token validation is performed in a before_request handler but introspection queries are excluded from that handler, the schema remains exposed. Attackers can also use automated tools to send targeted introspection queries and map the API surface, increasing the risk of discovering hidden or deprecated operations that lack proper authorization.
To illustrate, consider a Flask route where GraphQL execution is handled without verifying whether introspection is allowed for the current request context. A request like the following sent without a valid Bearer Token can expose the schema:
POST /graphql HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"query": "{ __schema { queryType { name } } }"
}
If the server responds with the schema, it indicates that introspection is active and improperly protected. When combined with weak Bearer Token usage—such as tokens being passed in URLs, logged inadvertently, or not enforced for all query types—the exposure becomes more severe. The combination of an open introspection capability and inconsistent Bearer Token enforcement creates a scenario where attackers can gather intelligence without needing valid credentials, violating the principle of least privilege and increasing the attack surface of the API.
Bearer Tokens-Specific Remediation in Flask — concrete code fixes
Securing GraphQL introspection in Flask when using Bearer Tokens requires explicit checks that prevent unauthorized introspection and ensure token validation is applied consistently. Below are concrete remediation steps and code examples to address these issues.
1. Enforce Bearer Token Validation for All Requests
Ensure that every incoming request to the GraphQL endpoint validates the presence and correctness of the Bearer Token before processing the query, including introspection queries.
from flask import request, jsonify
import jwt
SECRET_KEY = 'your-secret-key'
def validate_token():
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return None
token = auth_header.split(' ')[1]
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return payload
except jwt.InvalidTokenError:
return None
@app.before_request
def authenticate():
if request.path == '/graphql':
user = validate_token()
if not user:
return jsonify({'error': 'Unauthorized'}), 401
2. Disable or Restrict Introspection Based on Authorization
Modify the GraphQL execution logic to skip introspection when the request is not authorized or when introspection is disabled in production.
from graphql import graphql_sync
from graphql.type import GraphQLSchema
def execute_graphql(schema: GraphQLSchema, query: str, context_value):
# Only allow introspection if explicitly enabled and user is authenticated
from graphql import get_introspection_query
if query.strip() == get_introspection_query():
auth = validate_token()
if not auth:
raise Exception('Introspection requires authentication')
# Optionally restrict introspection to certain roles
if 'role' in auth and auth['role'] != 'admin':
raise Exception('Insufficient permissions for introspection')
result = graphql_sync(schema, query, context_value=context_value)
return result
3. Use Middleware to Conditionally Block Introspection
Implement a lightweight middleware that blocks introspection unless a feature flag or environment variable permits it, even for authenticated users.
import os
INTROSPECTION_ENABLED = os.getenv('ENABLE_INTROSPECTION', 'false').lower() == 'true'
@app.before_request
def block_introspection_in_production():
if not INTROSPECTION_ENABLED and request.path == '/graphql':
query = request.get_json(silent=True)
if query and 'query' in query:
from graphql import get_introspection_query
if query['query'].strip().startswith('query IntrospectionQuery'):
return jsonify({'error': 'Introspection is disabled'}), 403
4. Secure Token Handling Practices
Avoid common pitfalls with Bearer Tokens in Flask. Do not log tokens, pass them in URLs, or store them in insecure cookies. Always use HTTPS and set the Authorization header correctly.
# Correct usage: Bearer Token in header
curl -X POST https://api.example.com/graphql \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{"query": "{ users { id name } }"}'
5. Integrate with Flask Extensions
When using extensions like Flask-JWT or Flask-Security, configure them to apply to all routes, including GraphQL endpoints, and ensure they do not exclude introspection paths by default.
# Example with Flask-JWT
from flask_jwt_extended import JWTManager, jwt_required
app.config['JWT_SECRET_KEY'] = SECRET_KEY
jwt = JWTManager(app)
@app.route('/graphql', methods=['POST'])
@jwt_required()
def graphql_server():
# Token is already validated by jwt_required
return handle_graphql_request()
By combining strict Bearer Token validation with controlled introspection settings, Flask GraphQL APIs can remain functional for developers while reducing the risk of schema exposure.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |