Skip to content

StarletteIntegration overwrites the user set during a request (AuthenticationMiddleware) #6756

Description

@IAL32

How do you use Sentry?

Sentry SaaS (sentry.io)

Version

2.64.0 (also reproduces on current master, HEAD 44fb035)

Steps to Reproduce

With AuthenticationMiddleware + StarletteIntegration + send_default_pii=True, the integration extracts the user from the ASGI request and calls
scope.set_user(...) after the request handler has run (patch_authentication_middleware calls _add_user_to_sentry_scope after await old_call(...), sentry_sdk/integrations/starlette.py:411-412).

Because set_user replaces the scope user (starlette.py:392), any user the application set during the request via sentry_sdk.set_user(...) is discarded.
And Starlette's SimpleUser/BaseUser only exposes username, so id/email are lost.

repro:

import sentry_sdk
from sentry_sdk.integrations.starlette import StarletteIntegration
from starlette.applications import Starlette
from starlette.authentication import AuthCredentials, AuthenticationBackend, SimpleUser
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient

captured = []

class TokenBackend(AuthenticationBackend):
    async def authenticate(self, conn):
        # request principal only carries the token subject (username)
        return AuthCredentials(["authenticated"]), SimpleUser("token-subject-123")

async def endpoint(request):
    # app enriches the Sentry user during the request
    sentry_sdk.set_user({"id": "user_42", "email": "real@user.com", "username": "token-subject-123"})
    return PlainTextResponse("ok")

def capture(event, hint):
    captured.append(event.get("user"))
    return None  # drop, don't send

sentry_sdk.init(
    dsn="https://public@o0.ingest.sentry.io/0",
    send_default_pii=True,
    traces_sample_rate=1.0,
    integrations=[StarletteIntegration()],
    before_send_transaction=capture,
)

app = Starlette(
    routes=[Route("/", endpoint)],
    middleware=[Middleware(AuthenticationMiddleware, backend=TokenBackend())],
)
TestClient(app).get("/")
print(captured[-1])

pip freeze --all:

sentry-sdk==2.64.0
starlette==0.48.0

Expected Result

The request-derived user augments what the app set; app-set values are kept:

{'username': 'token-subject-123', 'id': 'user_42', 'email': 'real@user.com'}

Actual Result

The app-set id/email are clobbered — only the request principal survives:

{'username': 'token-subject-123'}

Real-world impact: on a FastMCP-based MCP server (bearer auth, stateless_http) every transaction loses user.email/user.id; only user.username (the token subject) survives, so traces can't be attributed to real users.

Metadata

Metadata

Assignees

Fields

No fields configured for issues without a type.

Projects

Status
Waiting for: Product Owner

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions