PostHog / PostHog/posthog

feat(oauth): structured logging for invalid_scope rejections at the AS

Open
#57,535 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
39.9k
Forks
3.4k
Avg merge
6h 51m
Merged PRs (30d)
232

Description

Context

Per the OAuth scope drift RFC's pre-flight #4, PostHog's AS doesn't emit invalid_scope rejections to the standard log stream. They come back as HTTP 302 redirects with ?error=invalid_scope in the redirect URL (per RFC 6749 § 4.1.2.1), but nothing logs the rejection on our side.

This means:

  • The RFC's success criterion ("zero invalid_scope over 30-day window") is unmeasurable today
  • We've been counting incidents by Slack reports + GitHub PRs, which is a lower bound
  • Carmen's case (#57509) was caught manually in review, not surfaced by alerting

Confirmed via query-logs against posthog-web-django: zero results for searchTerm invalid_scope over the past 24h, despite the in-flight #57509 PR being the resolution to a customer-reported invalid_scope event in the same window.

Change

In posthog/api/oauth/views.py, add structured logging at the point where the AS returns an invalid_scope redirect.

Two places to instrument depending on where DOT raises:

  1. The OAuthAuthorizationView (or its parent in DOT) — when validate_authorization_request() raises InvalidScopeError
  2. The exception handler that converts the OAuth error into the redirect URL

Add:

import structlog
import posthoganalytics
logger = structlog.get_logger(__name__)

# At the rejection point:
logger.warning(
    \"oauth.authorize.invalid_scope\",
    requested_scopes=requested_scopes_list,
    grantable_scopes=grantable_scopes_list,
    missing_scopes=list(set(requested_scopes_list) - set(grantable_scopes_list)),
    client_id=request.GET.get(\"client_id\", \"\"),
    user_agent=request.headers.get(\"User-Agent\", \"\")[:200],
)
posthoganalytics.capture_exception(
    Exception(\"OAuth invalid_scope rejection\"),
    properties={
        \"missing_scopes\": list(set(requested_scopes_list) - set(grantable_scopes_list)),
        \"client_id\": request.GET.get(\"client_id\", \"\"),
    },
)

Confirm the WARN-severity Loki query produces results within 7 days of deploy.

Acceptance

  • A query like SELECT count() FROM logs WHERE service.name = 'posthog-web-django' AND severity_text = 'warn' AND body ILIKE '%oauth.authorize.invalid_scope%' produces non-zero results once a real rejection occurs
  • The baseline metric for the RFC's success criterion is available within 7 days of this landing
  • Capture in error tracking surfaces the spike if there's a regression

Why

You can't measure what you don't observe. The RFC commits to "zero invalid_scope over 30 days" as the success metric for the structural cleanup; this issue makes that measurable.

Tracking

Parent: #57524
Project: https://github.com/orgs/PostHog/projects/194
RFC: https://github.com/PostHog/requests-for-comments-internal/blob/matt/oauth-scope-drift-rfc/engineering/2026-05-04-oauth-scope-drift.md

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in posthog/api/oauth/views.py at OAuthAuthorizationView and trace where validate_authorization_request() raises InvalidScopeError or where DOT converts the error into a redirect. Review the existing query-logs search and verify that a WARN query for oauth.authorize.invalid_scope returns a real rejection after deployment. Done means the structured log, error-tracking capture, and seven-day baseline are available.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, authentication, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.