OAuth Flow Metrics
Status: ✅ IMPLEMENTED Priority: 🟠 MEDIUM (Completed) Actual Time: 2-3 hours Implementation Date: 2026-01-07 Risk Level: LOW Impact: Enhanced observability for OAuth 2.1 authorization and token flows
Implementation Summary
OAuth flow metrics have been successfully implemented to provide comprehensive visibility into authorization requests, token exchanges, and dynamic client registrations.
What Was Implemented
- OAuth Authorization Requests Counter - Tracks authorization requests by result (success/error/invalid_client) in
src/services/metrics.ts - OAuth Token Exchanges Counter - Tracks token exchanges by grant type (authorization_code/refresh_token) and result in
src/services/metrics.ts - OAuth Token Exchange Duration Histogram - Measures IdP response time with buckets [0.1, 0.5, 1, 2, 5] seconds in
src/services/metrics.ts - Metrics Integration - Instrumented
src/routes/oauth-authorize.ts,src/routes/oauth-token.ts, andsrc/routes/oauth-register.ts - Comprehensive Tests - Added 6 test cases in
src/services/metrics.test.ts - Grafana Dashboard - Added 3 new panels to
grafana/seed-mcp-server-dashboard.json
Key Files Modified
src/services/metrics.ts- Metric definitionssrc/routes/oauth-authorize.ts- Authorization request tracking (8 instrumentation points)src/routes/oauth-token.ts- Token exchange tracking with duration measurementsrc/routes/oauth-register.ts- DCR trackingsrc/services/metrics.test.ts- Test coveragesrc/routes/oauth-authorize.test.ts- Updated metrics mockssrc/routes/oauth-token.test.ts- Updated metrics mockssrc/routes/oauth-register.test.ts- Updated metrics mocksgrafana/seed-mcp-server-dashboard.json- Dashboard panels (3 new panels)
Testing
- 6 comprehensive test cases covering all OAuth flow metrics
- Mock validation for proper metric instrumentation
- Test coverage: 93.02% overall (increased from 92.98%)
- All 771 tests passing via
npm run validate
Original Problem Statement
OAuth flow operations (authorization, token exchange, DCR) are logged but not exposed as Prometheus metrics. This limits operational visibility:
- Cannot track authorization success/failure rates
- No visibility into token exchange patterns by grant type
- Missing duration metrics for IdP token endpoint response times
- No breakdown of DCR registration outcomes
- Limited alerting capabilities for OAuth failures
From GAP_ANALYSIS.md section 11.2:
DCR registration counter exists but may not be exported. No authorization request tracking. No token exchange breakdown by grant type.
Proposed Solution
Add comprehensive metrics for all OAuth operations.
Metrics to Add
// src/services/metrics.ts
export const oauthAuthorizationRequests = new Counter({
name: "oauth_authorization_requests_total",
help: "Total OAuth authorization requests",
labelNames: ["result"], // success, error, invalid_client
registers: [register],
});
export const oauthTokenExchanges = new Counter({
name: "oauth_token_exchanges_total",
help: "Total OAuth token exchanges",
labelNames: ["grant_type", "result"], // authorization_code/refresh_token, success/failure
registers: [register],
});
export const oauthTokenExchangeDuration = new Histogram({
name: "oauth_token_exchange_duration_seconds",
help: "Duration of token exchanges with IdP",
labelNames: ["grant_type"],
buckets: [0.1, 0.5, 1, 2, 5],
registers: [register],
});Usage Examples
Authorization Tracking:
// src/routes/oauth-authorize.ts
// On validation error
if (!codeChallenge || codeChallenge.length < 43) {
oauthAuthorizationRequests.inc({ result: "error" });
return sendOAuthError(res, "invalid_request", "Invalid code_challenge");
}
// On success
oauthAuthorizationRequests.inc({ result: "success" });
res.redirect(302, redirectUrl.toString());Token Exchange Tracking:
// src/routes/oauth-token.ts
const timer = oauthTokenExchangeDuration.startTimer({ grant_type: grantType });
try {
const upstreamResponse = await proxyTokenRequest(proxyParams);
timer();
if (status === 200) {
oauthTokenExchanges.inc({ grant_type: grantType, result: "success" });
} else {
oauthTokenExchanges.inc({ grant_type: grantType, result: "failure" });
}
} catch (error) {
timer();
oauthTokenExchanges.inc({ grant_type: grantType, result: "failure" });
throw error;
}Sample Queries
# Authorization success rate
rate(oauth_authorization_requests_total{result="success"}[5m])
/ rate(oauth_authorization_requests_total[5m])
# Token exchange success rate by grant type
rate(oauth_token_exchanges_total{result="success"}[5m])
/ rate(oauth_token_exchanges_total[5m])
# P99 token exchange latency for authorization_code
histogram_quantile(0.99,
rate(oauth_token_exchange_duration_seconds_bucket{grant_type="authorization_code"}[5m])
)
# Invalid client rate
rate(oauth_authorization_requests_total{result="invalid_client"}[5m])
# Refresh token exchange rate
rate(oauth_token_exchanges_total{grant_type="refresh_token"}[5m])Grafana Dashboard Panels
Added 3 new panels to the Seed MCP Server dashboard:
Panel 33: OAuth Authorization Requests (Stacked Area)
- Location: Row 9, Left column (y=62)
- Type: Time series with stacked area visualization
- Queries:
- Success (green):
rate(oauth_authorization_requests_total{result="success"}[5m]) - Error (red):
rate(oauth_authorization_requests_total{result="error"}[5m]) - Invalid Client (orange):
rate(oauth_authorization_requests_total{result="invalid_client"}[5m])
- Success (green):
Panel 34: OAuth Token Exchanges (Stacked Area)
- Location: Row 9, Right column (x=12, y=62)
- Type: Time series with stacked area visualization
- Queries:
- Authorization Code Success:
rate(oauth_token_exchanges_total{grant_type="authorization_code", result="success"}[5m]) - Authorization Code Failure:
rate(oauth_token_exchanges_total{grant_type="authorization_code", result="failure"}[5m]) - Refresh Token Success:
rate(oauth_token_exchanges_total{grant_type="refresh_token", result="success"}[5m]) - Refresh Token Failure:
rate(oauth_token_exchanges_total{grant_type="refresh_token", result="failure"}[5m])
- Authorization Code Success:
Panel 35: OAuth Token Exchange Duration (Line Chart)
- Location: Row 10, Full width (y=70)
- Type: Time series with line visualization
- Queries:
- P50:
histogram_quantile(0.50, rate(oauth_token_exchange_duration_seconds_bucket[5m])) - P95:
histogram_quantile(0.95, rate(oauth_token_exchange_duration_seconds_bucket[5m])) - P99:
histogram_quantile(0.99, rate(oauth_token_exchange_duration_seconds_bucket[5m]))
- P50:
Acceptance Criteria
- [x] OAuth authorization request counter with result labels
- [x] OAuth token exchange counter with grant_type and result labels
- [x] OAuth token exchange duration histogram
- [x] DCR registration counter properly instrumented
- [x] Metrics integrated into all OAuth endpoints
- [x] Comprehensive test coverage (>90%)
- [x] Documentation with example queries
- [x] Grafana dashboard panels (3 new panels)
- [x] All tests passing (
npm run validate)
Estimated Effort
2-3 hours - Add metrics, integrate into OAuth routes, tests, documentation
Actual Effort: 2-3 hours (including debugging test mocks)
Implementation Details
Instrumentation Points
Authorization Endpoint (8 points):
- Configuration error (authorizationUrl not configured)
- Invalid code_challenge_method
- Invalid code_challenge length
- Invalid code_challenge format
- Invalid client (DCR client not found)
- Invalid redirect_uri
- Client validation error
- Success (redirect to IdP)
Token Endpoint (3 points):
- Timer start before IdP proxy request
- Success counter (HTTP 200 from IdP)
- Failure counter (non-200 or exception)
Register Endpoint (3 points):
- Validation failure
- Registration success
- Storage/server error
Test Coverage
Added 6 test cases in src/services/metrics.test.ts:
- Count OAuth authorization requests by result
- Count OAuth token exchanges by grant type and result
- Observe OAuth token exchange duration by grant type
- Use correct buckets for OAuth token exchange duration
- Count DCR registrations by result
- Track different grant types separately in token exchanges
Related Enhancements
- Token Refresh Metrics - Token refresh observability
- OAuth 2.1 Implementation - Base OAuth flow
- Dynamic Client Registration - DCR endpoint