Documentation
¶
Overview ¶
Package testutil provides reusable test infrastructure for oneauth integration tests. It is intended to be imported by downstream projects (mcpkit, relay, etc.) as well as oneauth's own test suites.
Two categories of helpers:
TestAuthServer — an in-process authorization server with RSA keys, JWKS, token endpoint, and AS metadata (RFC 8414).
Shared OAuth helpers — standalone functions that work against any RFC-compliant OAuth server (TestAuthServer, Keycloak, Auth0, etc.).
Note: The client/ package has production-grade equivalents (client.DiscoverAS, client.AuthClient.ClientCredentialsToken) with proper error handling, retries, and credential storage. These testutil helpers are intentionally simpler: they take *testing.T, call t.Fatal on error, and return plain structs for test ergonomics. They also include test-only functions (ParseJWTClaims, GetPasswordToken) that have no production equivalent.
<!-- design:start --> This package owns shared test infrastructure: an in-process authorization server and standalone OAuth/JWT helpers. It does NOT own production auth logic — the client/ package holds the hardened equivalents with retries, error handling, and credential storage. Everything here is tuned for test ergonomics: helpers take *testing.T and call t.Fatal, the server binds a random httptest port, and cleanup is automatic. Two test-only functions (ParseJWTClaims, GetPasswordToken) have no production counterpart.
TestAuthServer is the centerpiece: a real oneauth stack (RSA 2048 key, RS256 signing, JWKS, token endpoint, RFC 7662 introspection, app registration, and RFC 8414 AS metadata) wired into an httptest.Server. Construct it with NewTestAuthServer (registers t.Cleanup) or NewAuthServer (no *testing.T; caller must Close). Both accept functional Options.
A subtlety in the metadata Options: WithGrantTypesSupported and WithIssParameterSupported change only the advertised metadata, not server behavior — advertising a grant does not enable its handler, and there is no authorization endpoint to honor RFC 9207's iss= parameter. WithTrustedAssertionIssuers is the option that actually enables the jwt-bearer (RFC 7523) and token-exchange (RFC 8693) grants, and it auto-extends the advertised grant list unless a later WithGrantTypesSupported overrides it (last-option-wins on the slice).
Token minting (MintToken, MintTokenWithClaims, MintTokenForSubject) is a fast path that signs RS256 JWTs directly with the server key, bypassing the HTTP token endpoint and any network round-trip, while still setting a kid header that matches the JWKS-published key. MintTokenWithClaims sets iss/iat/exp defaults the caller may override, which is what makes negative tests (wrong issuer, expired, missing claims) easy to express.
The standalone helpers (DiscoverOIDC, GetClientCredentialsToken, GetPasswordToken, PostTokenEndpoint, FetchJWKS, ParseJWTClaims, ParseJWTHeader) target the HTTP surface of any RFC-compliant server, not just TestAuthServer. The JWT parsers decode without verifying signatures and are for test introspection only.
ENTITIES ¶
TestAuthServer — in-process httptest-backed OAuth AS exposing health, token, introspection, JWKS, AS-metadata, and app-registration endpoints.
NewTestAuthServer — builds a TestAuthServer bound to *testing.T with automatic t.Cleanup shutdown; the preferred entry point for tests.
NewAuthServer — builds a TestAuthServer without *testing.T for examples and benchmarks; the caller must call Close.
Option — functional option type for configuring a TestAuthServer.
WithAdminKey — sets the admin API key guarding the /apps endpoints (default "testutil-admin-key").
WithIssuer — sets the JWT issuer and AS-metadata issuer; the sentinel default is replaced with the live server URL after start.
WithAudience — sets the JWT audience claim (empty means unrestricted).
WithScopes — sets scopes_supported in AS metadata (advertisement only).
WithClaimsSupported — sets claims_supported in AS metadata, defaulting to the claims oneauth bearer tokens actually emit.
WithGrantTypesSupported — replaces the advertised grant_types_supported list; advertising a grant does not enable its handler.
WithIssParameterSupported — sets the RFC 9207 iss-parameter flag in metadata only; no authorization endpoint drives the actual behavior.
WithTrustedAssertionIssuers — enables jwt-bearer (RFC 7523) and token-exchange (RFC 8693) grants and auto-extends advertised grants unless a later WithGrantTypesSupported overrides them.
MintToken — mints a standard RS256 access JWT directly, bypassing the HTTP token endpoint; the kid header matches the JWKS key.
MintTokenWithClaims — mints an RS256 JWT from an arbitrary claims map with overridable iss/iat/exp defaults, enabling negative tests.
MintTokenForSubject — convenience minting for a subject and scopes, aimed at non-*testing.T example code.
URL — returns the server base URL (only known after the random port binds).
JWKSURL — returns the JWKS endpoint URL.
TokenEndpoint — returns the token endpoint URL.
AdminKey — returns the configured admin API key.
Issuer — returns the resolved JWT issuer (server URL when the default was used).
Close — stops the underlying httptest server; nil-safe.
OIDCConfig — parsed OAuth/OIDC AS metadata document (RFC 8414); the decode target for DiscoverOIDC.
TokenResponse — parsed OAuth token endpoint response (RFC 6749 §5.1).
DiscoverOIDC — fetches and parses the .well-known/openid-configuration document against any compliant AS, failing via t.Fatal.
GetClientCredentialsToken — acquires a token via the client_credentials grant (RFC 6749 §4.4), delegating to PostTokenEndpoint.
GetPasswordToken — acquires a token via the resource owner password grant (RFC 6749 §4.3); test-only with no production equivalent.
PostTokenEndpoint — sends a form POST to a token endpoint and decodes the JSON TokenResponse; shared core for the grant helpers.
FetchJWKS — fetches raw JWKS JSON as an untyped map (RFC 7517).
ParseJWTClaims — base64url-decodes a JWT payload without verifying the signature; test introspection only.
ParseJWTHeader — base64url-decodes a JWT header without verifying the signature; test introspection only. <!-- design:end -->
Index ¶
- func FetchJWKS(t *testing.T, jwksURI string) map[string]any
- func ParseJWTClaims(t *testing.T, tokenStr string) map[string]any
- func ParseJWTHeader(t *testing.T, tokenStr string) map[string]any
- type OIDCConfig
- type Option
- func WithAdminKey(key string) Option
- func WithAudience(aud string) Option
- func WithClaimsSupported(claims []string) Option
- func WithGrantTypesSupported(grants []string) Option
- func WithIssParameterSupported(supported bool) Option
- func WithIssuer(iss string) Option
- func WithScopes(scopes []string) Option
- func WithTrustedAssertionIssuers(issuers []apiauth.TrustedAssertionIssuer) Option
- type TestAuthServer
- func (s *TestAuthServer) AdminKey() string
- func (s *TestAuthServer) Close()
- func (s *TestAuthServer) Issuer() string
- func (s *TestAuthServer) JWKSURL() string
- func (s *TestAuthServer) MintToken(userID string, scopes []string) (string, error)
- func (s *TestAuthServer) MintTokenForSubject(subject string, scopes []string) (string, error)
- func (s *TestAuthServer) MintTokenWithClaims(claims jwt.MapClaims) (string, error)
- func (s *TestAuthServer) TokenEndpoint() string
- func (s *TestAuthServer) URL() string
- type TokenResponse
- func GetClientCredentialsToken(t *testing.T, tokenEndpoint, clientID, clientSecret string, scopes ...string) TokenResponse
- func GetPasswordToken(t *testing.T, tokenEndpoint, clientID, clientSecret, username, password string) TokenResponse
- func PostTokenEndpoint(t *testing.T, tokenEndpoint string, data url.Values) TokenResponse
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FetchJWKS ¶
FetchJWKS fetches the raw JWKS JSON from the given URI and returns it as a map. Calls t.Fatal on any error.
func ParseJWTClaims ¶
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.
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.
type Option ¶
type Option func(*config)
Option configures a TestAuthServer.
func WithAdminKey ¶
WithAdminKey sets the admin API key required for app registration endpoints. Default: "testutil-admin-key".
func WithAudience ¶
WithAudience sets the JWT audience claim on minted tokens. Default: "" (no audience restriction).
func WithClaimsSupported ¶ added in v0.0.83
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 WithGrantTypesSupported ¶ added in v0.0.82
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 WithIssParameterSupported ¶ added in v0.0.82
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.
Note: TestAuthServer does NOT currently implement an authorization endpoint, so the *behavior* RFC 9207 §2 mandates (`iss=` in redirects) is not yet driven by this flag — the flag affects only the metadata advertisement. Setting it true on a real AS that does NOT actually emit `iss` in authorization responses is a spec violation.
func WithIssuer ¶
WithIssuer sets the JWT issuer claim and the issuer field in AS metadata. Default: "testutil-issuer" (overridden to server URL after start).
func WithScopes ¶
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
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:
type TestAuthServer ¶
type TestAuthServer struct {
// Server is the underlying httptest.Server. Use URL() for the base URL.
Server *httptest.Server
// APIAuth is the configured API authentication handler (RS256).
APIAuth *apiauth.APIAuth
// 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/register — app registration 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/register or /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.
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.
func PostTokenEndpoint ¶ added in v0.0.73
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.