oauthserver

package
v0.16.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 23, 2026 License: MIT Imports: 21 Imported by: 0

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

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 JWKS

type JWKS struct {
	Keys []JWK `json:"keys"`
}

JWKS represents a JSON Web Key Set.

type KeyRing

type KeyRing struct {
	// contains filtered or unexported fields
}

KeyRing manages RSA key pairs for JWT signing with rotation support.

func NewKeyRing

func NewKeyRing() (*KeyRing, error)

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

func (kr *KeyRing) GetActiveKid() string

GetActiveKid returns the currently active key ID.

func (*KeyRing) GetJWKS

func (kr *KeyRing) GetJWKS() *JWKS

GetJWKS returns the public keys in JWK Set format.

func (*KeyRing) GetKey

func (kr *KeyRing) GetKey(kid string) (*rsa.PrivateKey, bool)

GetKey returns a key by ID.

func (*KeyRing) GetPublicKey

func (kr *KeyRing) GetPublicKey() *rsa.PublicKey

GetPublicKey returns the public key of the currently active key for token verification.

func (*KeyRing) RemoveKey

func (kr *KeyRing) RemoveKey(kid string) error

RemoveKey removes a key from the key ring. Tokens signed with this key will fail verification.

func (*KeyRing) RotateKey

func (kr *KeyRing) RotateKey() (string, error)

RotateKey adds a new signing key and makes it active. The old key remains valid for verification. Returns the new key ID.

func (*KeyRing) RotateTo

func (kr *KeyRing) RotateTo(kid string) error

RotateTo switches the active signing key to the specified key ID.

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.

Directories

Path Synopsis
cmd
server command
Standalone OAuth test server for browser-based testing (Playwright).
Standalone OAuth test server for browser-based testing (Playwright).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL