Feature: 007-oauth-e2e-testing Date: 2025-12-02
The main test server instance managing all OAuth endpoints.
| Field | Type | Description |
|---|---|---|
| server | *http.Server | Underlying HTTP server |
| addr | string | Bound address (e.g., "127.0.0.1:12345") |
| issuerURL | string | Full issuer URL (e.g., "http://127.0.0.1:12345") |
| options | Options | Configuration for this server instance |
| keyRing | *KeyRing | RSA key management for JWT signing |
| clients | map[string]*Client | Registered OAuth clients (client_id -> Client) |
| authCodes | map[string]*AuthorizationCode | Pending authorization codes |
| deviceCodes | map[string]*DeviceCode | Pending device codes |
| mu | sync.RWMutex | Protects concurrent access |
State Transitions: None (stateless per request, state stored in maps)
Configuration for the test OAuth server behavior.
| Field | Type | Default | Description |
|---|---|---|---|
| EnableAuthCode | bool | true | Enable authorization code flow |
| EnableDeviceCode | bool | true | Enable device code flow |
| EnableDCR | bool | true | Enable dynamic client registration |
| EnableClientCredentials | bool | true | Enable client credentials grant |
| EnableRefreshToken | bool | true | Issue refresh tokens |
| AccessTokenExpiry | time.Duration | 3600s | Access token lifetime |
| RefreshTokenExpiry | time.Duration | 86400s | Refresh token lifetime |
| AuthCodeExpiry | time.Duration | 600s | Authorization code lifetime |
| DeviceCodeExpiry | time.Duration | 300s | Device code lifetime |
| DeviceCodeInterval | int | 5 | Polling interval in seconds |
| DefaultScopes | []string | ["read"] | Scopes if none requested |
| SupportedScopes | []string | ["read","write","admin"] | All valid scopes |
| RequirePKCE | bool | true | Require PKCE for auth code flow |
| ErrorMode | ErrorMode | (empty) | Error injection configuration |
| DetectionMode | DetectionMode | Discovery | How OAuth is advertised |
| ValidUsers | map[string]string | {"testuser":"testpass"} | Valid username/password pairs |
Configuration for injecting specific OAuth errors.
| Field | Type | Description |
|---|---|---|
| TokenInvalidClient | bool | Return invalid_client on token requests |
| TokenInvalidGrant | bool | Return invalid_grant on token requests |
| TokenInvalidScope | bool | Return invalid_scope on token requests |
| TokenServerError | bool | Return HTTP 500 on token requests |
| TokenSlowResponse | time.Duration | Delay before token response |
| TokenUnsupportedGrant | bool | Return unsupported_grant_type |
| AuthAccessDenied | bool | Return error=access_denied on authorize |
| AuthInvalidRequest | bool | Return error=invalid_request on authorize |
| DCRInvalidRedirectURI | bool | Reject registration with bad redirect |
| DCRInvalidScope | bool | Reject registration with bad scope |
| DeviceSlowPoll | bool | Return slow_down on device polling |
| DeviceExpired | bool | Return expired_token on device polling |
How the OAuth server advertises its capabilities.
| Value | Description |
|---|---|
| Discovery | Serve /.well-known/oauth-authorization-server |
| WWWAuthenticate | Return 401 with WWW-Authenticate header on protected resource |
| Explicit | No discovery; endpoints must be configured explicitly |
| Both | Serve both discovery and WWW-Authenticate |
A registered OAuth client (pre-configured or dynamically registered).
| Field | Type | Description |
|---|---|---|
| ClientID | string | Unique client identifier |
| ClientSecret | string | Client secret (empty for public clients) |
| RedirectURIs | []string | Allowed callback URLs |
| GrantTypes | []string | Allowed grant types |
| ResponseTypes | []string | Allowed response types |
| Scopes | []string | Allowed scopes for this client |
| ClientName | string | Human-readable name |
| IsPublic | bool | True if public client (no secret) |
| CreatedAt | time.Time | Registration timestamp |
Validation Rules:
ClientIDmust be non-empty and uniqueRedirectURIsmust contain at least one valid URI- URIs must not contain fragments (
#) localhostand127.0.0.1allowed for test callbacks
Ephemeral code issued during authorization flow.
| Field | Type | Description |
|---|---|---|
| Code | string | The authorization code value |
| ClientID | string | Associated client |
| RedirectURI | string | Redirect URI used in request |
| Scopes | []string | Granted scopes |
| CodeChallenge | string | PKCE code challenge |
| CodeChallengeMethod | string | PKCE method (S256) |
| Resource | string | RFC 8707 resource indicator |
| State | string | Client state parameter |
| ExpiresAt | time.Time | Code expiration time |
| Used | bool | Whether code has been exchanged |
State Transitions:
- Created → Used (on token exchange)
- Created → Expired (on timeout)
Device authorization code for device flow.
| Field | Type | Description |
|---|---|---|
| DeviceCode | string | Secret device code (for polling) |
| UserCode | string | User-facing code (e.g., "ABCD-1234") |
| ClientID | string | Associated client |
| Scopes | []string | Requested scopes |
| Resource | string | RFC 8707 resource indicator |
| VerificationURI | string | URL for user to visit |
| VerificationURIComplete | string | URL with user_code pre-filled |
| ExpiresAt | time.Time | Code expiration time |
| Interval | int | Minimum polling interval (seconds) |
| Status | DeviceCodeStatus | Current status |
| ApprovedScopes | []string | Scopes approved by user (if approved) |
State Transitions:
pending → approved (user approves)
pending → denied (user denies)
pending → expired (timeout)
| Value | Description |
|---|---|
| Pending | Awaiting user action |
| Approved | User approved the request |
| Denied | User denied the request |
| Expired | Code has expired |
Response from token endpoint (success case).
| Field | Type | Description |
|---|---|---|
| AccessToken | string | JWT access token |
| TokenType | string | Always "Bearer" |
| ExpiresIn | int | Seconds until expiry |
| RefreshToken | string | Refresh token (if enabled) |
| Scope | string | Space-separated granted scopes |
Response from token endpoint (error case).
| Field | Type | Description |
|---|---|---|
| Error | string | Error code (e.g., "invalid_client") |
| ErrorDescription | string | Human-readable description |
| ErrorURI | string | Optional URI for more info |
Manages RSA key pairs for JWT signing with rotation support.
| Field | Type | Description |
|---|---|---|
| keys | map[string]*rsa.PrivateKey | Key ID to private key mapping |
| activeKid | string | Currently active key for signing |
| mu | sync.RWMutex | Protects concurrent access |
Operations:
AddKey(kid string, key *rsa.PrivateKey): Add a new keyRotateTo(kid string): Switch active signing keyRemoveKey(kid string): Remove a key (for rotation testing)GetJWKS(): Return public keys in JWK Set formatSignToken(claims): Sign JWT with active key
Return value from Start() function.
| Field | Type | Description |
|---|---|---|
| IssuerURL | string | Full issuer URL for configuration |
| ClientID | string | Pre-registered test client ID |
| ClientSecret | string | Pre-registered test client secret |
| PublicClientID | string | Pre-registered public client ID (PKCE) |
| JWKSURL | string | URL to fetch JWKS |
| AuthorizationEndpoint | string | /authorize URL |
| TokenEndpoint | string | /token URL |
| RegistrationEndpoint | string | /registration URL (if DCR enabled) |
| DeviceAuthorizationEndpoint | string | /device_authorization URL (if enabled) |
| Shutdown | func() error | Function to stop server |
OAuth 2.0 Authorization Server Metadata (RFC 8414).
| Field | JSON Key | Type | Description |
|---|---|---|---|
| Issuer | issuer | string | Issuer identifier URL |
| AuthorizationEndpoint | authorization_endpoint | string | Authorization endpoint URL |
| TokenEndpoint | token_endpoint | string | Token endpoint URL |
| JWKSURI | jwks_uri | string | JWKS endpoint URL |
| RegistrationEndpoint | registration_endpoint | string | DCR endpoint URL |
| DeviceAuthorizationEndpoint | device_authorization_endpoint | string | Device authz URL |
| ScopesSupported | scopes_supported | []string | Supported scopes |
| ResponseTypesSupported | response_types_supported | []string | Supported response types |
| GrantTypesSupported | grant_types_supported | []string | Supported grant types |
| CodeChallengeMethodsSupported | code_challenge_methods_supported | []string | PKCE methods |
| TokenEndpointAuthMethodsSupported | token_endpoint_auth_methods_supported | []string | Client auth methods |
OAuthTestServer
├── Options (1:1)
├── KeyRing (1:1)
├── Clients (1:N)
│ └── Client
├── AuthorizationCodes (1:N)
│ └── AuthorizationCode
│ └── Client (N:1)
└── DeviceCodes (1:N)
└── DeviceCode
└── Client (N:1)
Access tokens issued by the test server:
{
"iss": "http://127.0.0.1:12345",
"sub": "testuser",
"aud": "https://api.example.com", // From resource indicator
"client_id": "test-client-id",
"scope": "read write",
"exp": 1733123456,
"iat": 1733119856,
"jti": "unique-token-id"
}The test OAuth server is in-memory only:
- All state (clients, codes, tokens) stored in Go maps
- State is cleared when server shuts down
- No persistence needed for test scenarios
- Thread-safe access via
sync.RWMutex
mcpproxy's token storage (unchanged):
- Uses existing
internal/storage/BBolt implementation PersistentTokenStoreininternal/oauth/persistent_token_store.go- Tests verify tokens are persisted correctly