feat(metrics): mcp_auth_authorize_initiated_total funnel counter#27
Merged
Conversation
Closes T4.3 from misc/next-steps.md. Adds the GET-side of the
consent funnel so operators can answer:
- "How many /authorize requests are entering the IdP fork?"
- "Are clients abandoning the consent page?"
- "What fraction take the consent vs silent path?"
Increment is placed after every parameter validation (client_id,
redirect_uri, response_type, resource, PKCE, state) and BEFORE
the consent/silent fork. Pre-validation rejections — unknown
client_id, unregistered redirect_uri, response_type != "code",
invalid resource, state-missing in strict mode — happen earlier
and correctly skip the counter; they're surfaced via
access_denied_total{reason=...} instead.
Funnel math:
initiated → consent_decisions{approved|denied} → tokens_issued
(consent fork)
↘ tokens_issued (silent fork — no consent step)
Derived rates: consent_rate = sum(consent_decisions) /
authorize_initiated; silent_rate = 1 - consent_rate.
A spike in initiated without a matching spike downstream points
at consent abandonment or callback failures (IdP-side, browser
errors, replay rejections, throttle 503s).
- Counter has no labels — minimal Prometheus cardinality. A
future operator who wants per-fork breakdown can derive it
from consent_decisions presence/absence (consent fork only)
or add a `path` label later (backwards-compatible).
- Tests: 6 in total — 1 each for consent/silent fork + a
table-driven test for the four pre-funnel reject classes
(unknown client, unsupported response_type, invalid
resource, state-missing strict). The negative test
structurally pins that the counter sits AFTER all
validation, so a future refactor that moves the increment
line breaks at least one row.
- README observability section updated with the funnel
derivation formula.
Promote the funnel counter to a CounterVec with `path` label
(consent | silent) so operators can read per-fork rate
directly from PromQL without deriving it via the presence of
consent_decisions. Forensic signal added to the active-IdP-
session-phishing threat row: consent abandonment is the leading
indicator and now has an explicit metric formula.
- metrics: AuthorizeInitiated → CounterVec, label `path`.
- handlers/authorize.go: each fork increments its own label.
Pre-validation rejections still skip the counter entirely.
- tests: per-fork tests pin the right label moves AND the
other does not (no cross-bleed). Pre-funnel-reject test
asserts neither label moves.
- README observability entry: PromQL fork-rate +
consent-abandonment formulas using the labelled metric.
- handlers/authorize.go comment reworded — increment lives
inside each fork branch, not before the if statement.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes T4.3 from `misc/next-steps.md` — the last item in the Tier 1-2 backlog.
Adds the GET-side of the consent funnel. Operators can now answer:
Where the increment fires
After every parameter validation (`client_id`, `redirect_uri`, `response_type`, `resource`, PKCE, `state`) and BEFORE the consent/silent fork. Pre-validation rejections — unknown `client_id`, unregistered `redirect_uri`, `response_type != "code"`, invalid `resource`, state-missing in strict mode — happen earlier and correctly skip the counter; those are surfaced via `access_denied_total{reason=...}` instead.
Funnel math
```
initiated → consent_decisions{approved|denied} → tokens_issued{authorization_code}
(consent fork)
↘ tokens_issued (silent fork — no consent step)
```
Derived rates:
A spike in `initiated` without a matching spike downstream points at consent abandonment, callback failures (IdP-side, browser errors), replay rejections, or throttle 503s.
Implementation
Tests
Six tests across three semantic classes:
The negative test structurally pins that the counter sits AFTER all validation. A future refactor that moves the increment above any reject path breaks at least one row.
Docs
`README.md` observability section gains an entry with the funnel-rate derivation formulas.
Test plan