Documentation
¶
Overview ¶
Package oauthserver provides a reusable OAuth 2.1 test server for E2E testing.
The test server implements the following OAuth 2.1 specifications:
- RFC 6749: OAuth 2.0 Authorization Framework
- RFC 7636: PKCE (Proof Key for Code Exchange)
- RFC 7591: Dynamic Client Registration (DCR)
- RFC 8414: OAuth Authorization Server Metadata
- RFC 8628: Device Authorization Grant
- RFC 8707: Resource Indicators
- RFC 9728: OAuth Protected Resource Metadata
Quick Start ¶
Create a test server with default options:
func TestOAuth(t *testing.T) {
server := oauthserver.Start(t, oauthserver.Options{})
defer server.Shutdown()
// Use server.AuthorizationEndpoint, server.TokenEndpoint, etc.
// Use server.ClientID and server.ClientSecret for confidential clients
// Use server.PublicClientID for public clients with PKCE
}
Features ¶
The test server supports multiple OAuth flows:
- Authorization Code with PKCE (default enabled)
- Client Credentials Grant
- Device Authorization Grant (RFC 8628)
- Dynamic Client Registration (RFC 7591)
- Refresh Token Grant
- Resource Indicators (RFC 8707)
Configuration ¶
Use Options to customize server behavior:
server := oauthserver.Start(t, oauthserver.Options{
EnableDeviceCode: true, // Enable device code flow
EnableDCR: true, // Enable dynamic client registration
AccessTokenExpiry: 5*time.Minute,
ErrorMode: oauthserver.ErrorMode{
TokenInvalidClient: true, // Inject invalid_client errors
},
})
Detection Modes ¶
The server supports multiple OAuth detection methods:
- Discovery: Serves /.well-known/oauth-authorization-server
- WWWAuthenticate: Returns 401 with WWW-Authenticate header on /protected
- Explicit: No automatic discovery (for manual configuration tests)
- Both: Discovery + WWW-Authenticate
Error Injection ¶
Use ErrorMode to inject specific OAuth errors for testing error handling:
server := oauthserver.Start(t, oauthserver.Options{
ErrorMode: oauthserver.ErrorMode{
TokenInvalidClient: true, // invalid_client error
TokenInvalidGrant: true, // invalid_grant error
TokenServerError: true, // HTTP 500 error
TokenSlowResponse: 5*time.Second, // Add delay
},
})
JWKS Rotation ¶
Test key rotation scenarios:
server := oauthserver.Start(t, oauthserver.Options{})
defer server.Shutdown()
// Get initial key ID
jwks1 := fetchJWKS(server.JWKSURL)
// Rotate to a new key
newKid, _ := server.Server.KeyRing.RotateKey()
// Both keys are now in JWKS
jwks2 := fetchJWKS(server.JWKSURL)
Device Code Flow ¶
For programmatic testing without browser interaction:
server := oauthserver.Start(t, oauthserver.Options{
EnableDeviceCode: true,
})
defer server.Shutdown()
// Request device code
resp := requestDeviceCode(server)
// Programmatically approve
server.Server.ApproveDeviceCode(resp.UserCode)
// Poll for token
token := pollForToken(server, resp.DeviceCode)
Dynamic Client Registration ¶
server := oauthserver.Start(t, oauthserver.Options{
EnableDCR: true,
})
defer server.Shutdown()
// Register a new client
client := registerClient(server.IssuerURL+"/registration", req)
// Use registered client for auth code flow
authCodeFlow(server, client.ClientID, client.ClientSecret)
Package oauthserver provides a test OAuth 2.1 server for mcpproxy E2E testing. It implements RFC 6749, 7636, 7591, 8414, 8628, 8707.
Index ¶
- type AuthCodeInfo
- type AuthorizationCode
- type Client
- type ClientConfig
- type ClientRegistrationRequest
- type ClientRegistrationResponse
- type DetectionMode
- type DeviceAuthorizationResponse
- type DeviceCode
- type DeviceCodeStatus
- type DiscoveryMetadata
- type ErrorMode
- type JWK
- type JWKS
- type KeyRing
- func (kr *KeyRing) AddKey(kid string, key *rsa.PrivateKey) error
- func (kr *KeyRing) GetActiveKey() (string, *rsa.PrivateKey)
- func (kr *KeyRing) GetActiveKid() string
- func (kr *KeyRing) GetJWKS() *JWKS
- func (kr *KeyRing) GetKey(kid string) (*rsa.PrivateKey, bool)
- func (kr *KeyRing) GetPublicKey() *rsa.PublicKey
- func (kr *KeyRing) RemoveKey(kid string) error
- func (kr *KeyRing) RotateKey() (string, error)
- func (kr *KeyRing) RotateTo(kid string) error
- type LoginPageData
- type OAuthError
- type OAuthTestServer
- func (s *OAuthTestServer) ApproveDeviceCode(userCode string) error
- func (s *OAuthTestServer) DenyDeviceCode(userCode string) error
- func (s *OAuthTestServer) ExpireDeviceCode(userCode string) error
- func (s *OAuthTestServer) GetAuthorizationCodes() []AuthCodeInfo
- func (s *OAuthTestServer) GetClient(clientID string) (*Client, bool)
- func (s *OAuthTestServer) GetIssuedTokens() []TokenInfo
- func (s *OAuthTestServer) RegisterClient(cfg ClientConfig) (*Client, error)
- func (s *OAuthTestServer) SetErrorMode(mode ErrorMode)
- func (s *OAuthTestServer) Shutdown() error
- type Options
- type RefreshTokenData
- type ServerResult
- type TokenClaims
- type TokenErrorResponse
- type TokenInfo
- type TokenResponse
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuthCodeInfo ¶
type AuthCodeInfo struct {
Code string
ClientID string
Scopes []string
ExpiresAt time.Time
Used bool
}
AuthCodeInfo contains information about an authorization code (for debugging).
type AuthorizationCode ¶
type AuthorizationCode struct {
Code string
ClientID string
RedirectURI string
Scopes []string
CodeChallenge string
CodeChallengeMethod string
Resource string // RFC 8707 resource indicator
State string
Subject string // Username who authorized
ExpiresAt time.Time
Used bool
}
AuthorizationCode represents an ephemeral code issued during authorization flow.
func (*AuthorizationCode) IsExpired ¶
func (ac *AuthorizationCode) IsExpired() bool
IsExpired checks if the authorization code has expired.
type Client ¶
type Client struct {
ClientID string
ClientSecret string // Empty for public clients
RedirectURIs []string
GrantTypes []string
ResponseTypes []string
Scopes []string
ClientName string
IsPublic bool
CreatedAt time.Time
}
Client represents a registered OAuth client.
type ClientConfig ¶
type ClientConfig struct {
ClientID string
ClientSecret string // Empty for public clients
RedirectURIs []string
GrantTypes []string // Default: ["authorization_code", "refresh_token"]
ResponseTypes []string // Default: ["code"]
Scopes []string // Default: options.SupportedScopes
ClientName string
}
ClientConfig defines a pre-registered OAuth client.
type ClientRegistrationRequest ¶
type ClientRegistrationRequest struct {
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
ClientName string `json:"client_name,omitempty"`
Scope string `json:"scope,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
}
ClientRegistrationRequest represents a DCR request.
type ClientRegistrationResponse ¶
type ClientRegistrationResponse struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret,omitempty"`
ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
ClientSecretExpiresAt int64 `json:"client_secret_expires_at,omitempty"`
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types"`
ResponseTypes []string `json:"response_types"`
ClientName string `json:"client_name,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"`
}
ClientRegistrationResponse represents a successful DCR response.
type DetectionMode ¶
type DetectionMode int
DetectionMode controls how OAuth is advertised to clients.
const ( // Discovery serves /.well-known/oauth-authorization-server Discovery DetectionMode = iota // WWWAuthenticate returns 401 with WWW-Authenticate header on /protected WWWAuthenticate // Explicit provides no discovery; client must configure endpoints manually Explicit // Both serves discovery AND returns WWW-Authenticate Both )
type DeviceAuthorizationResponse ¶
type DeviceAuthorizationResponse struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
ExpiresIn int `json:"expires_in"`
Interval int `json:"interval,omitempty"`
}
DeviceAuthorizationResponse represents the response from the device authorization endpoint.
type DeviceCode ¶
type DeviceCode struct {
DeviceCode string
UserCode string
ClientID string
Scopes []string
Resource string // RFC 8707 resource indicator
VerificationURI string
VerificationURIComplete string
ExpiresAt time.Time
Interval int
Status DeviceCodeStatus
ApprovedScopes []string // Scopes approved by user (if approved)
Subject string // Username who approved (if approved)
}
DeviceCode represents a device authorization code for device flow.
func (*DeviceCode) IsExpired ¶
func (dc *DeviceCode) IsExpired() bool
IsExpired checks if the device code has expired.
type DeviceCodeStatus ¶
type DeviceCodeStatus int
DeviceCodeStatus represents the state of a device code.
const ( // Pending - awaiting user action Pending DeviceCodeStatus = iota // Approved - user approved the request Approved // Denied - user denied the request Denied // Expired - code has expired Expired )
type DiscoveryMetadata ¶
type DiscoveryMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
JWKSURI string `json:"jwks_uri"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint,omitempty"`
ScopesSupported []string `json:"scopes_supported"`
ResponseTypesSupported []string `json:"response_types_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
}
DiscoveryMetadata represents OAuth 2.0 Authorization Server Metadata (RFC 8414).
type ErrorMode ¶
type ErrorMode struct {
// Token endpoint errors
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`
// Authorization endpoint errors
AuthAccessDenied bool // Return `error=access_denied` on authorize
AuthInvalidRequest bool // Return `error=invalid_request` on authorize
// DCR endpoint errors
DCRInvalidRedirectURI bool // Reject registration with bad redirect
DCRInvalidScope bool // Reject registration with bad scope
// Device code errors
DeviceSlowPoll bool // Return `slow_down` on device polling
DeviceExpired bool // Return `expired_token` on device polling
}
ErrorMode configures error injection for testing error handling.
type JWK ¶
type JWK struct {
Kty string `json:"kty"`
Use string `json:"use"`
Kid string `json:"kid"`
Alg string `json:"alg"`
N string `json:"n"`
E string `json:"e"`
}
JWK represents a JSON Web Key.
type KeyRing ¶
type KeyRing struct {
// contains filtered or unexported fields
}
KeyRing manages RSA key pairs for JWT signing with rotation support.
func NewKeyRing ¶
NewKeyRing creates a new KeyRing with an initial RSA key.
func (*KeyRing) AddKey ¶
func (kr *KeyRing) AddKey(kid string, key *rsa.PrivateKey) error
AddKey adds a new RSA key to the key ring.
func (*KeyRing) GetActiveKey ¶
func (kr *KeyRing) GetActiveKey() (string, *rsa.PrivateKey)
GetActiveKey returns the currently active private key and its ID.
func (*KeyRing) GetActiveKid ¶
GetActiveKid returns the currently active key ID.
func (*KeyRing) GetKey ¶
func (kr *KeyRing) GetKey(kid string) (*rsa.PrivateKey, bool)
GetKey returns a key by ID.
func (*KeyRing) GetPublicKey ¶
GetPublicKey returns the public key of the currently active key for token verification.
func (*KeyRing) RemoveKey ¶
RemoveKey removes a key from the key ring. Tokens signed with this key will fail verification.
type LoginPageData ¶
type LoginPageData struct {
ClientID string
ClientName string
RedirectURI string
ResponseType string
Scope string
State string
CodeChallenge string
CodeChallengeMethod string
Resource string
Scopes []string
Error string
}
LoginPageData contains data for rendering the login page.
type OAuthError ¶
type OAuthError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}
OAuthError represents a generic OAuth error.
type OAuthTestServer ¶
type OAuthTestServer struct {
// contains filtered or unexported fields
}
OAuthTestServer is the main test server instance managing all OAuth endpoints.
func (*OAuthTestServer) ApproveDeviceCode ¶
func (s *OAuthTestServer) ApproveDeviceCode(userCode string) error
ApproveDeviceCode marks a device code as approved. Use for programmatic device flow testing without UI interaction.
func (*OAuthTestServer) DenyDeviceCode ¶
func (s *OAuthTestServer) DenyDeviceCode(userCode string) error
DenyDeviceCode marks a device code as denied.
func (*OAuthTestServer) ExpireDeviceCode ¶
func (s *OAuthTestServer) ExpireDeviceCode(userCode string) error
ExpireDeviceCode marks a device code as expired.
func (*OAuthTestServer) GetAuthorizationCodes ¶
func (s *OAuthTestServer) GetAuthorizationCodes() []AuthCodeInfo
GetAuthorizationCodes returns pending authorization codes (for debugging).
func (*OAuthTestServer) GetClient ¶
func (s *OAuthTestServer) GetClient(clientID string) (*Client, bool)
GetClient retrieves a client by ID.
func (*OAuthTestServer) GetIssuedTokens ¶
func (s *OAuthTestServer) GetIssuedTokens() []TokenInfo
GetIssuedTokens returns all tokens issued (for verification in tests).
func (*OAuthTestServer) RegisterClient ¶
func (s *OAuthTestServer) RegisterClient(cfg ClientConfig) (*Client, error)
RegisterClient programmatically registers a client. Returns the registered client with generated credentials.
func (*OAuthTestServer) SetErrorMode ¶
func (s *OAuthTestServer) SetErrorMode(mode ErrorMode)
SetErrorMode updates error injection at runtime.
func (*OAuthTestServer) Shutdown ¶
func (s *OAuthTestServer) Shutdown() error
Shutdown stops the OAuth test server.
type Options ¶
type Options struct {
// Flow toggles (all true by default when using defaults)
EnableAuthCode bool
EnableDeviceCode bool
EnableDCR bool
EnableClientCredentials bool
EnableRefreshToken bool
// Token lifetimes
AccessTokenExpiry time.Duration // Default: 1 hour
RefreshTokenExpiry time.Duration // Default: 24 hours
AuthCodeExpiry time.Duration // Default: 10 minutes
DeviceCodeExpiry time.Duration // Default: 5 minutes
DeviceCodeInterval int // Default: 5 seconds
// Scopes
DefaultScopes []string // Default: ["read"]
SupportedScopes []string // Default: ["read", "write", "admin"]
// Security
RequirePKCE bool // Default: true
RequireResourceIndicator bool // RFC 8707: Require resource parameter (default: false)
// Error injection
ErrorMode ErrorMode
// Detection mode
DetectionMode DetectionMode // Default: Discovery
// Test credentials
ValidUsers map[string]string // Default: {"testuser": "testpass"}
// Pre-registered clients (in addition to auto-generated test client)
Clients []ClientConfig
}
Options configures the OAuth test server behavior.
func DefaultOptions ¶
func DefaultOptions() Options
DefaultOptions returns Options with sensible defaults for testing.
type RefreshTokenData ¶
type RefreshTokenData struct {
Token string
ClientID string
Subject string
Scopes []string
Resource string
ExpiresAt time.Time
}
RefreshTokenData stores refresh token information.
func (*RefreshTokenData) IsExpired ¶
func (rt *RefreshTokenData) IsExpired() bool
IsExpired checks if the refresh token has expired.
type ServerResult ¶
type ServerResult struct {
// Server URLs
IssuerURL string
AuthorizationEndpoint string
TokenEndpoint string
JWKSURL string
RegistrationEndpoint string // Empty if DCR disabled
DeviceAuthorizationEndpoint string // Empty if device code disabled
ProtectedResourceURL string // For WWW-Authenticate detection tests
// Pre-registered test client (confidential)
ClientID string
ClientSecret string
// Pre-registered public client (for PKCE flows)
PublicClientID string
// Shutdown function - must be called after tests
Shutdown func() error
// Internal server reference for advanced testing
Server *OAuthTestServer
}
ServerResult contains everything needed to configure a test client.
func Start ¶
func Start(t *testing.T, opts Options) *ServerResult
Start creates and starts a new OAuth test server. The server listens on an ephemeral port on localhost. Returns ServerResult containing URLs and credentials for testing. Call result.Shutdown() to stop the server after tests complete.
func StartOnPort ¶
func StartOnPort(t *testing.T, port int, opts Options) *ServerResult
StartOnPort creates and starts an OAuth test server on a specific port. This version is for standalone server usage (not in tests). Pass nil for t when running outside of tests.
type TokenClaims ¶
type TokenClaims struct {
jwt.RegisteredClaims
ClientID string `json:"client_id,omitempty"`
Scope string `json:"scope,omitempty"`
}
TokenClaims represents the claims in an access token.
type TokenErrorResponse ¶
type TokenErrorResponse struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
ErrorURI string `json:"error_uri,omitempty"`
}
TokenErrorResponse represents a token error response.
type TokenInfo ¶
type TokenInfo struct {
AccessToken string
RefreshToken string
ClientID string
Subject string
Scopes []string
Resource string
IssuedAt time.Time
ExpiresAt time.Time
}
TokenInfo contains information about an issued token (for testing).
type TokenResponse ¶
type TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope string `json:"scope,omitempty"`
}
TokenResponse represents a successful token response.