testutil

package
v0.1.35 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FetchJWKS

func FetchJWKS(t *testing.T, jwksURI string) map[string]any

FetchJWKS fetches the raw JWKS JSON from the given URI and returns it as a map. Calls t.Fatal on any error.

See: https://www.rfc-editor.org/rfc/rfc7517

func ParseJWTClaims

func ParseJWTClaims(t *testing.T, tokenStr string) map[string]any

ParseJWTClaims decodes the payload (claims) of a JWT without verifying the signature. For test introspection only — never use in production. Calls t.Fatal on any error.

func ParseJWTHeader

func ParseJWTHeader(t *testing.T, tokenStr string) map[string]any

ParseJWTHeader decodes the header of a JWT without verifying the signature. For test introspection only — never use in production. Calls t.Fatal on any error.

Types

type OIDCConfig

type OIDCConfig struct {
	Issuer                                     string   `json:"issuer"`
	TokenEndpoint                              string   `json:"token_endpoint"`
	JWKSURI                                    string   `json:"jwks_uri"`
	AuthorizationEndpoint                      string   `json:"authorization_endpoint,omitempty"`
	IntrospectionEndpoint                      string   `json:"introspection_endpoint,omitempty"`
	RegistrationEndpoint                       string   `json:"registration_endpoint,omitempty"`
	ScopesSupported                            []string `json:"scopes_supported,omitempty"`
	ClaimsSupported                            []string `json:"claims_supported,omitempty"`
	TokenEndpointAuthMethodsSupported          []string `json:"token_endpoint_auth_methods_supported,omitempty"`
	TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
}

OIDCConfig holds discovered OIDC / OAuth Authorization Server endpoints. See: https://www.rfc-editor.org/rfc/rfc8414#section-2

func DiscoverOIDC

func DiscoverOIDC(t *testing.T, issuerURL string) OIDCConfig

DiscoverOIDC fetches and parses the OpenID Connect / OAuth Authorization Server Metadata document from issuerURL/.well-known/openid-configuration. Calls t.Fatal on any error.

See: https://www.rfc-editor.org/rfc/rfc8414

type Option

type Option func(*config)

Option configures a TestAuthServer.

func WithAdminKey

func WithAdminKey(key string) Option

WithAdminKey sets the admin API key required for app registration endpoints. Default: "testutil-admin-key".

func WithAllowPlainPKCE added in v0.1.32

func WithAllowPlainPKCE(allow bool) Option

WithAllowPlainPKCE opts the test AS into OAuth 2.0 `plain` PKCE on the /authorize endpoint (RFC 7636 §4.4). OAuth 2.1 §7.5 retired plain; this option exists for conformance fixtures that need to exercise the OAuth 2.0 escape hatch documented under capability-gating umbrella #344. AS metadata `code_challenge_methods_supported` extends to ["S256", "plain"] when enabled. Production deployments never set this.

func WithAudience

func WithAudience(aud string) Option

WithAudience sets the JWT audience claim on minted tokens. Default: "" (no audience restriction).

func WithAudienceFunc added in v0.1.31

func WithAudienceFunc(fn func() string) Option

WithAudienceFunc registers a closure consulted on every token mint and validation. A non-empty return takes precedence over the eager WithAudience value; an empty return falls back to it.

Use this when the audience is not known at construction time — the canonical case is an in-process AS whose `aud` must equal a resource server URL only allocated after `httptest.NewServer` runs. Build the AS once, then teach the closure to look up the current value when the RS comes online. The caller owns the storage and synchronisation; testutil only plumbs the closure to OneAuthConfig.AudienceFunc.

See docs/MIGRATION.md "Late-binding the audience" for the full pattern.

func WithAuthorizeAutoApproveSubject added in v0.1.30

func WithAuthorizeAutoApproveSubject(subject string) Option

WithAuthorizeAutoApproveSubject sets the subject the auto-approve path binds to issued codes. Empty falls back to "e2e-user".

func WithAuthorizeEnabled added in v0.1.30

func WithAuthorizeEnabled(enabled bool) Option

WithAuthorizeEnabled mounts the RFC 6749 §4.1 authorization-code flow endpoint (`GET / POST /authorize`) on the test server. When enabled the AS metadata advertises `authorization_endpoint`, extends `response_types_supported` to include "code", and adds "authorization_code" to `grant_types_supported`.

The mounted flow auto-approves all requests with the subject set by WithAuthorizeAutoApproveSubject (default: "e2e-user"). This is suitable for conformance fixtures and in-process tests — NEVER for production deployments.

func WithAuthorizeRedirectOverride added in v0.1.30

func WithAuthorizeRedirectOverride(fn func(values url.Values)) Option

WithAuthorizeRedirectOverride installs a hook that mutates the /authorize redirect's query values before they are URL-encoded. Lets conformance scenarios test misbehaving-AS branches that a correct production AS cannot simulate — e.g. "AS advertises authorization_response_iss_parameter_supported=true in metadata but does NOT emit iss in the redirect" — by stripping or corrupting individual values.

Production deployments never set this. Mirrors the PR 191 pattern.

func WithClaimsSupported added in v0.0.83

func WithClaimsSupported(claims []string) Option

WithClaimsSupported sets the `claims_supported` field in AS metadata (OIDC Discovery 1.0 §3). Default: the claims OneAuth's bearer tokens already emit (sub, iss, aud, exp, iat, jti, scope, client_id).

func WithConfidentialClient added in v0.1.34

func WithConfidentialClient(clientID, secret string) Option

WithConfidentialClient registers a confidential client (token_endpoint_auth_method=client_secret_post) with the given secret, and wires the AS with an AppStore so confidential-client authentication is enforced on the grants that consult it (jwt-bearer / token-exchange, issue 356; device_code / authorization_code, issue 266).

Opt-in: absent this option the AS runs with no AppStore, preserving the public / assertion-only behavior existing tests rely on.

func WithGrantTypesSupported added in v0.0.82

func WithGrantTypesSupported(grants []string) Option

WithGrantTypesSupported overrides the `grant_types_supported` field advertised in AS metadata (RFC 8414). Use this when enabling a grant beyond the default (`client_credentials`) — e.g., advertising the jwt-bearer (RFC 7523 §2.1) or token-exchange (RFC 8693) grants alongside the default.

The values supplied REPLACE the default. Callers that want to keep `client_credentials` and add more must include it in the slice.

Note: advertising a grant in metadata does NOT enable its handler at the token endpoint. Pair this with WithTrustedAssertionIssuers to actually serve jwt-bearer / token-exchange requests.

func WithIDJAGIssuanceSelfSigned added in v0.1.33

func WithIDJAGIssuanceSelfSigned(ttl time.Duration) Option

WithIDJAGIssuanceSelfSigned opts the token-exchange grant into ID-JAG issuance signed with the test server's OWN RS256 key — the key already published in the server's JWKS. This is the production-realistic path: a redeeming AS can discover the ID-JAG signing key over JWKS by `kid` (TrustedAssertionIssuer.KeyFunc) with only the issuer URL, no pre-shared static key.

ttl bounds the ID-JAG lifetime (<=0 uses the library default). Takes precedence over WithIDJAGIssuer when both are set.

func WithIDJAGIssuer added in v0.1.33

func WithIDJAGIssuer(issuer apiauth.IDJAGIssuer) Option

WithTrustedAssertionIssuers configures the AS to accept jwt-bearer (RFC 7523 §2.1) and token-exchange (RFC 8693) grants from the listed upstream IdPs. Each entry binds an issuer URL to a public key (or KeyFunc) so the AS can verify assertion signatures.

When the supplied slice is non-empty, this Option AUTOMATICALLY extends the advertised `grant_types_supported` to include the two new grant URIs (so callers don't need to remember the pair). Callers can still pass WithGrantTypesSupported AFTER this option to fully replace the advertised list — last-option-wins for the slice.

See:

WithIDJAGIssuer opts the token-exchange grant into ID-JAG issuance (requested_token_type=urn:ietf:params:oauth:token-type:id-jag) for the MCP Enterprise-Managed Authorization flow (SEP-990). The issuer signs ID-JAGs with the IdP key the caller supplies; register that key's public half at the redeeming AS via WithTrustedAssertionIssuers so stage 2 can verify it.

Meaningful only alongside WithTrustedAssertionIssuers, which supplies the subject_token (id_token) issuers this AS validates on stage 1.

See: draft-ietf-oauth-identity-assertion-authz-grant-04

func WithIssParameterSupported added in v0.0.82

func WithIssParameterSupported(supported bool) Option

WithIssParameterSupported sets the `authorization_response_iss_parameter_supported` field in AS metadata (RFC 9207 §3). Mitigates mix-up attacks against clients that interact with multiple ASes by signaling that the AS includes an `iss` parameter on every authorization response.

When paired with WithAuthorizeEnabled(true), this flag ALSO drives the wire behavior — the /authorize redirect carries `iss=<issuer>` per RFC 9207 §2 — so the AS's advertisement matches what callers observe. To simulate the misbehaving-AS scenario (advertises but does not emit, or emits without advertising), combine with WithAuthorizeRedirectOverride to mutate the redirect query per-scenario.

func WithIssuer

func WithIssuer(iss string) Option

WithIssuer sets the JWT issuer claim and the issuer field in AS metadata. Default: "testutil-issuer" (overridden to server URL after start).

func WithScopes

func WithScopes(scopes []string) Option

WithScopes sets the scopes_supported field in AS metadata. Default: ["read", "write", "admin"].

func WithTrustedAssertionIssuers added in v0.0.82

func WithTrustedAssertionIssuers(issuers []apiauth.TrustedAssertionIssuer) Option

type TestAuthServer

type TestAuthServer struct {
	// Server is the underlying httptest.Server. Use URL() for the base URL.
	Server *httptest.Server

	// OneAuth is the configured transport-independent core (RS256).
	OneAuth *apiauth.OneAuth

	// TokenEndpointHandler is the HTTP handler mounted at /api/token.
	// Distinct name from the TokenEndpoint() method which returns the URL.
	TokenEndpointHandler *apiauth.TokenEndpointHandler

	// KeyStore holds the server's RSA key and any registered app keys.
	KeyStore keys.KeyStorage

	// Registrar manages app registrations and serves the /apps/ endpoints.
	Registrar *admin.AppRegistrar
	// contains filtered or unexported fields
}

TestAuthServer is an in-process oneauth authorization server for integration tests. It generates an RSA 2048 key pair, serves JWKS, issues tokens via the client_credentials grant, and provides OAuth AS metadata (RFC 8414).

The server is cleaned up automatically via t.Cleanup — callers never need to call Close manually.

Endpoints served:

GET  /_ah/health                          — health check
POST /api/token                           — token endpoint (client_credentials)
POST /oauth/introspect                    — token introspection (RFC 7662)
GET  /.well-known/jwks.json               — JWKS public key (RFC 7517)
GET  /.well-known/openid-configuration    — AS metadata (RFC 8414)
POST /apps/dcr                            — dynamic client registration (RFC 7591)

func NewAuthServer added in v0.0.74

func NewAuthServer(opts ...Option) (*TestAuthServer, error)

NewTestAuthServer creates and starts an in-process authorization server with an RSA 2048 key pair. The server is automatically shut down via t.Cleanup when the test completes.

The server signs JWTs with RS256 and serves the public key via JWKS. Apps can be registered via /apps/dcr (RFC 7591), and tokens can be obtained via /api/token (client_credentials grant). NewAuthServer creates an in-process OAuth authorization server without requiring *testing.T. Use this in standalone examples, benchmarks, or any non-test context. The caller must call Close() when done.

For test code, prefer NewTestAuthServer which auto-registers cleanup.

func NewTestAuthServer

func NewTestAuthServer(t *testing.T, opts ...Option) *TestAuthServer

NewTestAuthServer creates an in-process authorization server for tests. Cleanup is registered via t.Cleanup — the server is stopped automatically.

func (*TestAuthServer) AdminKey

func (s *TestAuthServer) AdminKey() string

AdminKey returns the admin API key configured for this server.

func (*TestAuthServer) Close added in v0.0.74

func (s *TestAuthServer) Close()

Close stops the underlying HTTP server.

func (*TestAuthServer) Issuer

func (s *TestAuthServer) Issuer() string

Issuer returns the JWT issuer configured for this server.

func (*TestAuthServer) JWKSURL

func (s *TestAuthServer) JWKSURL() string

JWKSURL returns the JWKS endpoint URL.

func (*TestAuthServer) MintToken

func (s *TestAuthServer) MintToken(userID string, scopes []string) (string, error)

MintToken creates a valid RS256 JWT with standard claims for the given user and scopes. This is a fast path that bypasses the HTTP token endpoint — no network round-trip is involved.

The token includes: sub, iss, aud (if configured), type ("access"), scopes, iat, exp (15 min), jti, and a kid header matching the server's JWKS-published key.

See: https://www.rfc-editor.org/rfc/rfc7519 (JWT)

func (*TestAuthServer) MintTokenForSubject added in v0.0.74

func (s *TestAuthServer) MintTokenForSubject(subject string, scopes []string) (string, error)

MintTokenForSubject creates a valid RS256 JWT for the given subject and scopes. The token has iss=server issuer, aud=server audience, exp=15 min. Use this in standalone examples that don't have *testing.T.

func (*TestAuthServer) MintTokenWithClaims

func (s *TestAuthServer) MintTokenWithClaims(claims jwt.MapClaims) (string, error)

MintTokenWithClaims creates an RS256 JWT with arbitrary claims. Standard defaults (iss, iat, exp) are set but can be overridden by the provided claims map. This is useful for testing edge cases: wrong issuer, expired tokens, missing claims, etc.

The kid header is always set to match the server's JWKS key.

See: https://www.rfc-editor.org/rfc/rfc7519 (JWT)

func (*TestAuthServer) TokenEndpoint

func (s *TestAuthServer) TokenEndpoint() string

TokenEndpoint returns the token endpoint URL.

func (*TestAuthServer) URL

func (s *TestAuthServer) URL() string

URL returns the base URL of the test auth server (e.g., "http://127.0.0.1:PORT").

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	Scope        string `json:"scope"`
	RefreshToken string `json:"refresh_token,omitempty"`
}

TokenResponse holds the token endpoint response fields. See: https://www.rfc-editor.org/rfc/rfc6749#section-5.1

func GetClientCredentialsToken

func GetClientCredentialsToken(t *testing.T, tokenEndpoint, clientID, clientSecret string, scopes ...string) TokenResponse

GetClientCredentialsToken acquires a token using the OAuth 2.0 client_credentials grant. Works against any RFC 6749-compliant token endpoint. Calls t.Fatal on any error.

See: https://www.rfc-editor.org/rfc/rfc6749#section-4.4

func GetPasswordToken

func GetPasswordToken(t *testing.T, tokenEndpoint, clientID, clientSecret, username, password string) TokenResponse

GetPasswordToken acquires a token using the OAuth 2.0 resource owner password credentials grant. Works against any RFC 6749-compliant token endpoint. Calls t.Fatal on any error.

See: https://www.rfc-editor.org/rfc/rfc6749#section-4.3

func PostTokenEndpoint added in v0.0.73

func PostTokenEndpoint(t *testing.T, tokenEndpoint string, data url.Values) TokenResponse

PostTokenEndpoint sends a form POST to an OAuth token endpoint and decodes the JSON response into a TokenResponse. Use this for any grant type — the convenience functions (GetClientCredentialsToken, GetPasswordToken) delegate here. Calls t.Fatal on HTTP or decode errors.

Jump to

Keyboard shortcuts

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