Documentation
¶
Overview ¶
Package crpcauthtesting provides test helpers for stdcrpcauthfx that use real JWT signing and validation. A local JWKS server is started so the real authentication code path runs in tests.
Index ¶
- Constants
- Variables
- func CallAs[Req, Resp any](ctx context.Context, tb testing.TB, signer *TokenSigner, subject string, ...) (*connect.Response[Resp], error)
- func Clock() jwt.Clock
- type TokenSigner
- func (s *TokenSigner) Sign(tb testing.TB, subject string, scopes []string) string
- func (s *TokenSigner) SignClaims(tb testing.TB, subject string, scopes []string, tenantID string) string
- func (s *TokenSigner) SignWithClaims(tb testing.TB, subject string, scopes []string, extraClaims map[string]any) string
- func (s *TokenSigner) SignWithPermissions(tb testing.TB, subject string, permissions []string) string
- func (s *TokenSigner) SignWithScopeAndPermissions(tb testing.TB, subject string, scopes []string, permissions []string) string
Constants ¶
const ( // TestAudience is the audience claim used in test JWTs. TestAudience = "urn:test:audience" // TestKeyID is the key ID used for signing test JWTs. TestKeyID = "test-key" )
Variables ¶
var TestClockTime = time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)
TestClockTime is the fixed wall-clock time used for JWT validation in tests.
Functions ¶
func CallAs ¶ added in v0.0.229
func CallAs[Req, Resp any]( ctx context.Context, tb testing.TB, signer *TokenSigner, subject string, scopes []string, tenantID string, method func(context.Context, *connect.Request[Req]) (*connect.Response[Resp], error), msg *Req, ) (*connect.Response[Resp], error)
CallAs invokes a typed Connect RPC method as the caller identified by subject/scopes/tenantID.
The three identity parameters mirror stdcrpcauthfx.Claims so the test-side abstraction stays in lockstep with the production middleware: subject becomes the JWT "sub" claim, scopes are space-joined into the "scope" claim, and tenantID (when non-empty) is written under the JWT path the signer was configured with at construction time. Passing a non-empty tenantID to a signer without a configured tenant claim path is a test failure (see TokenSigner.SignClaims).
The supplied method is typically a method value of a generated Connect client (e.g. client.WhoAmI), which keeps the helper free of any per-service knowledge.
CallAs is deliberately authenticated-only: tests for the unauthenticated path should call the client method directly so the absence of a token is explicit at the call site. Tests that need to mint tokens with claim shapes outside the Claims model (unknown claim paths, malformed payloads, etc.) should call the lower-level TokenSigner methods (Sign, SignWithClaims, SignWithPermissions, …) and decorate their own connect.Request.
Types ¶
type TokenSigner ¶
type TokenSigner struct {
// contains filtered or unexported fields
}
TokenSigner signs JWTs using a test RSA key pair.
func NewJWKSServer ¶
func NewJWKSServer(tb testing.TB, tenantClaim string) (serverURL string, signer *TokenSigner)
NewJWKSServer starts a local JWKS httptest.Server and returns the server URL and a TokenSigner. The server is automatically closed when the test completes. The server URL can be used as STDCRPCAUTH_TOKEN_ISSUER and the server serves the public key at /.well-known/jwks.json.
tenantClaim is the JWT claim path under which SignClaims will place tenantID values. Pass the same string used for STDCRPCAUTH_TENANT_CLAIM in the application's fx env so test signing and production reading line up. Pass "" for tests that don't exercise tenancy.
func (*TokenSigner) Sign ¶
Sign creates a signed JWT with the given subject and scopes (via the "scope" claim).
func (*TokenSigner) SignClaims ¶ added in v0.0.229
func (s *TokenSigner) SignClaims(tb testing.TB, subject string, scopes []string, tenantID string) string
SignClaims creates a signed JWT shaped after stdcrpcauthfx.Claims. It sets the "sub" and space-joined "scope" claims and, when tenantID is non-empty, writes it under the JWT path the signer was configured with at construction time (matching what production reads from STDCRPCAUTH_TENANT_CLAIM). An empty tenantID is omitted, mirroring production behavior where an absent tenant claim leaves Claims.TenantID empty.
Passing a non-empty tenantID to a signer that was created without a tenant claim path is a test failure: there is no path to write it under, so the production middleware would silently drop it. Failing fast catches that wiring drift.
func (*TokenSigner) SignWithClaims ¶ added in v0.0.226
func (s *TokenSigner) SignWithClaims( tb testing.TB, subject string, scopes []string, extraClaims map[string]any, ) string
SignWithClaims creates a signed JWT with the given subject, scopes (via the "scope" claim) and any number of extra claims. Useful for testing custom claim paths such as tenancy identifiers (e.g. "https://example.com/org_id"). Claim keys with "/" or other path-like characters are passed through verbatim so namespaced Auth0 claims work as-is.
func (*TokenSigner) SignWithPermissions ¶ added in v0.0.219
func (s *TokenSigner) SignWithPermissions(tb testing.TB, subject string, permissions []string) string
SignWithPermissions creates a signed JWT with the given subject and permissions (via the "permissions" claim as a JSON array, matching the Auth0 SPA token format).
func (*TokenSigner) SignWithScopeAndPermissions ¶ added in v0.0.220
func (s *TokenSigner) SignWithScopeAndPermissions( tb testing.TB, subject string, scopes []string, permissions []string, ) string
SignWithScopeAndPermissions creates a signed JWT with both the "scope" claim (space-separated) and the "permissions" claim (JSON array), matching the Auth0 m2m token format where the same scope can appear in both claims.