HIGH graphql introspectionfastapijwt tokens

Graphql Introspection in Fastapi with Jwt Tokens

Graphql Introspection in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability

GraphQL introspection in a FastAPI application exposes the schema, queries, and types of your API through the standard __schema and __type operations. When combined with JWT-based authentication, a misconfiguration can unintentionally expose introspection to authenticated contexts where it should be limited or disabled. Even when endpoints expect a valid JWT, developers sometimes allow introspection at the same route without additional constraints, relying solely on authentication to limit access.

This combination creates risk because authenticated requests with valid JWTs can still trigger introspection queries, giving attackers detailed insight into the API structure, including queries, mutations, subscriptions, and field relationships. Attackers can use this information to discover sensitive operations, locate hidden fields, and plan further attacks such as injection or IDOR. If introspection is enabled broadly and JWT validation does not explicitly restrict it, the authentication mechanism fails to reduce the attack surface effectively.

In FastAPI, GraphQL servers are often implemented using libraries like Strawberry or GraphQL-core. If the GraphQL handler is mounted under a protected route that validates JWTs but introspection is left enabled without scope or role checks, any client that possesses a valid token can still retrieve the full schema. This is especially problematic when the JWT does not enforce fine-grained permissions for introspection, or when the validation middleware applies authentication but not authorization for the introspection operation itself.

For example, a FastAPI route that decodes a JWT and passes user context to GraphQL resolvers may still allow introspection queries to proceed if the GraphQL schema exposes __schema without additional guards. The presence of a valid JWT does not inherently prevent introspection; without explicit schema-level or route-level controls, attackers can leverage authenticated introspection to map the API.

To mitigate this specific combination, you must ensure that introspection is either disabled in production or tightly controlled based on JWT claims, roles, or scopes. This requires explicit schema configuration and route-level checks rather than relying on authentication alone.

Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes

Remediation focuses on ensuring JWT validation explicitly governs introspection access. In FastAPI, you can conditionally disable introspection or restrict it based on token claims. Below are concrete code examples that show how to implement this safely.

Example 1: Disable introspection by default in Strawberry

import strawberry
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

security = HTTPBearer()

def verify_jwt(credentials: HTTPAuthorizationCredentials = Depends(security)):
    # Replace with actual JWT validation logic
    if credentials.credentials != "valid-token":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return {"sub": "user", "scopes": ["read"]}

@strawberry.type
class Query:
    @strawberry.field
    def public_field(self) -> str:
        return "ok"

# Disable introspection explicitly
schema = strawberry.Schema(query=Query, enable_introspection=False)

app = FastAPI()

@app.post("/graphql")
async def graphql_endpoint(
    data: str,
    token_info: dict = Depends(verify_jwt),
):
    # You can further inspect token_info["scopes"] to allow introspection selectively
    return {"data": data}

Example 2: Conditional introspection with GraphQL-core and FastAPI

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from graphql import GraphQLSchema, build_schema, graphql_sync
from graphql.execution.base import ExecutionResult

security = HTTPBearer()

def verify_jwt(credentials: HTTPAuthorizationCredentials = Depends(security)):
    if credentials.credentials != "valid-token":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return {"sub": "user", "scopes": ["read", "introspect"]}

schema_str = """
type Query {
    public: String
    secret: String @auth(requires: ADMIN)
}
"""

schema: GraphQLSchema = build_schema(schema_str)

app = FastAPI()

def get_introspection_data(allowed: bool) -> dict:
    if not allowed:
        return {}
    from graphql import graphql_sync
    from graphql.type import build_schema, GraphQLSchema
    result = graphql_sync(schema, "__schema { queryType { name } }")
    return result.data or {}

@app.post("/graphql")
async def graphql_endpoint(
    query: str,
    token_info: dict = Depends(verify_jwt),
):
    can_introspect = "introspect" in token_info.get("scopes", [])
    if query.strip().startswith("__") and not can_introspect:
        raise HTTPException(status_code=400, detail="Introspection not allowed")
    # Execute query with schema, passing context with token_info if needed
    result: ExecutionResult = graphql_sync(schema, query)
    return {"data": result.data, "errors": [str(e) for e in result.errors]}

Example 3: Middleware to enforce introspection rules per request

from fastapi import FastAPI, Request, HTTPException, status
from fastapi.middleware import Middleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
import jwt

app = FastAPI()

JWT_PUBLIC_KEY = "your-public-key"

def decode_jwt(token: str) -> dict:
    try:
        return jwt.decode(token, JWT_PUBLIC_KEY, algorithms=["RS256"], audience="api", issuer="auth.example.com")
    except jwt.PyJWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")

@app.middleware("http")
async def enforce_introspection_policy(request: Request, call_next):
    if request.url.path == "/graphql" and request.method == "POST":
        auth_header = request.headers.get("authorization")
        if auth_header:
            token = auth_header.split(" ")[1]
            claims = decode_jwt(token)
            body = await request.body()
            # parse GraphQL query from body (simplified)
            import json
            data = json.loads(body)
            query = data.get("query", "")
            if query.strip().startswith("__") and not claims.get("can_introspect"):
                raise HTTPException(status_code=403, detail="Introspection forbidden by policy")
        else:
            raise HTTPException(status_code=401, detail="Missing authorization")
    response = await call_next(request)
    return response

Remediation summary

  • Disable introspection globally in production unless explicitly required.
  • Use JWT scopes or roles to gate introspection and avoid allowing it for general authenticated users.
  • Validate and filter introspection queries at the route or middleware layer, not only at the schema level.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Can authenticated introspection ever be safe?
It can be safe only when tightly scoped: disable introspection by default and enable it conditionally based on JWT scopes or roles, and validate introspection queries at the route or middleware level.
Does JWT validation alone prevent GraphQL introspection leaks?
No. JWT authentication confirms identity but does not restrict introspection. You must explicitly disable or gate introspection in the GraphQL schema and/or route handling.