Skip to content

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

← Back to Enhancements


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

  1. OAuth Authorization Requests Counter - Tracks authorization requests by result (success/error/invalid_client) in src/services/metrics.ts
  2. OAuth Token Exchanges Counter - Tracks token exchanges by grant type (authorization_code/refresh_token) and result in src/services/metrics.ts
  3. OAuth Token Exchange Duration Histogram - Measures IdP response time with buckets [0.1, 0.5, 1, 2, 5] seconds in src/services/metrics.ts
  4. Metrics Integration - Instrumented src/routes/oauth-authorize.ts, src/routes/oauth-token.ts, and src/routes/oauth-register.ts
  5. Comprehensive Tests - Added 6 test cases in src/services/metrics.test.ts
  6. Grafana Dashboard - Added 3 new panels to grafana/seed-mcp-server-dashboard.json

Key Files Modified

  • src/services/metrics.ts - Metric definitions
  • src/routes/oauth-authorize.ts - Authorization request tracking (8 instrumentation points)
  • src/routes/oauth-token.ts - Token exchange tracking with duration measurement
  • src/routes/oauth-register.ts - DCR tracking
  • src/services/metrics.test.ts - Test coverage
  • src/routes/oauth-authorize.test.ts - Updated metrics mocks
  • src/routes/oauth-token.test.ts - Updated metrics mocks
  • src/routes/oauth-register.test.ts - Updated metrics mocks
  • grafana/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

typescript
// 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:

typescript
// 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:

typescript
// 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

promql
# 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])

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])

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]))

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):

  1. Configuration error (authorizationUrl not configured)
  2. Invalid code_challenge_method
  3. Invalid code_challenge length
  4. Invalid code_challenge format
  5. Invalid client (DCR client not found)
  6. Invalid redirect_uri
  7. Client validation error
  8. Success (redirect to IdP)

Token Endpoint (3 points):

  1. Timer start before IdP proxy request
  2. Success counter (HTTP 200 from IdP)
  3. Failure counter (non-200 or exception)

Register Endpoint (3 points):

  1. Validation failure
  2. Registration success
  3. Storage/server error

Test Coverage

Added 6 test cases in src/services/metrics.test.ts:

  1. Count OAuth authorization requests by result
  2. Count OAuth token exchanges by grant type and result
  3. Observe OAuth token exchange duration by grant type
  4. Use correct buckets for OAuth token exchange duration
  5. Count DCR registrations by result
  6. Track different grant types separately in token exchanges

Released under the MIT License.