Documentation
¶
Overview ¶
Package axiam — OIDC / SSO relying-party helpers (CONTRACT.md §12, contract 1.4).
The nine canonical §12 operations, under the exact §12.2 Go names, as methods on the existing *Client: OidcDiscover, OidcBegin, OidcExchange, OidcRefresh, LoginClientCredentials, Introspect, Revoke, SsoStart, SsoComplete.
Everything besides the PKCE/CSPRNG primitives (oidc_pkce.go) and the issuer/audience/time/nonce checklist (oidc_idtoken.go) is reuse, not reimplementation (§12 forbids forking):
- transport + §2 error mapping + §3 CSRF + §4 cookie jar + §5 tenant header + §6 TLS -> the SAME *http.Client / doRequest/newRequest choke point client.go already built;
- §12.4 signature verification -> internal/jwks.Verifier, extended (never forked) with a raw-payload entry point;
- §7/§12.5 redaction -> the existing Sensitive type.
Index ¶
- Constants
- Variables
- type AccessCheck
- type AccessResult
- type AuthError
- type AuthorizationRequest
- type AuthzError
- type Client
- func (c *Client) BatchCheck(ctx context.Context, reqs []AccessCheck) ([]AccessResult, error)
- func (c *Client) Can(ctx context.Context, action, resourceID string, scope ...string) (bool, error)
- func (c *Client) CheckAccess(ctx context.Context, action, resourceID string, scope ...string) (bool, string, error)
- func (c *Client) CheckAccessAs(ctx context.Context, subjectID, action, resourceID string, scope ...string) (bool, string, error)
- func (c *Client) Introspect(ctx context.Context, params IntrospectParams) (IntrospectionResult, error)
- func (c *Client) Login(ctx context.Context, email, password string) (LoginResult, error)
- func (c *Client) LoginClientCredentials(ctx context.Context, params LoginClientCredentialsParams) (OidcTokenSet, error)
- func (c *Client) Logout(ctx context.Context) error
- func (c *Client) OidcBegin(configuration OidcConfiguration, params OidcBeginParams) (AuthorizationRequest, error)
- func (c *Client) OidcDiscover(ctx context.Context) (OidcConfiguration, error)
- func (c *Client) OidcExchange(ctx context.Context, params OidcExchangeParams) (OidcTokenSet, error)
- func (c *Client) OidcRefresh(ctx context.Context, params OidcRefreshParams) (OidcTokenSet, error)
- func (c *Client) Refresh(ctx context.Context) error
- func (c *Client) Revoke(ctx context.Context, params RevokeParams) error
- func (c *Client) SsoComplete(ctx context.Context, params SsoCompleteParams) (SsoCompleteResult, error)
- func (c *Client) SsoStart(ctx context.Context, params SsoStartParams) (SsoStartResult, error)
- func (c *Client) VerifyMfa(ctx context.Context, mfaToken Sensitive, code string) (LoginResult, error)
- type IDTokenClaims
- type IDTokenFailureReason
- type IntrospectParams
- type IntrospectionResult
- type JWKSVerifier
- type LoginClientCredentialsParams
- type LoginResult
- type MemoryOidcStateStore
- type NetworkError
- type OAuthProtocolError
- type OidcBeginParams
- type OidcConfiguration
- type OidcExchangeParams
- type OidcRefreshParams
- type OidcStateEntry
- type OidcStateStore
- type OidcTokenSet
- type Option
- func WithClientCertificate(certPEM, keyPEM []byte) Option
- func WithCustomCA(pem []byte) Option
- func WithHTTPClient(hc *http.Client) Option
- func WithLogger(logger *slog.Logger) Option
- func WithOidcClientID(clientID string) Option
- func WithOidcClientSecret(clientSecret string) Option
- func WithOidcClockSkew(seconds int) Option
- func WithOidcDiscoveryTTL(ttl time.Duration) Option
- func WithOrgID(id uuid.UUID) Option
- func WithOrgSlug(slug string) Option
- func WithTimeout(d time.Duration) Option
- type RevokeParams
- type Sensitive
- type SsoCompleteParams
- type SsoCompleteResult
- type SsoStartParams
- type SsoStartResult
Constants ¶
const MaxIDTokenClockSkewSec = 60
MaxIDTokenClockSkewSec is the CONTRACT.md §12.4 rule 5 ceiling for permitted ID-token clock skew: 60 seconds. WithOidcClockSkew clamps any larger configured value down to this ceiling; it is also the default when unconfigured.
const MinOidcDiscoveryTTL = 5 * time.Minute
MinOidcDiscoveryTTL is the CONTRACT.md §12.3 rule 6 FLOOR for the OIDC discovery-document cache TTL: 5 minutes. WithOidcDiscoveryTTL raises any smaller configured value up to this floor; it is also the default when unconfigured.
const OidcStateTTL = 10 * time.Minute
OidcStateTTL is the contract-mandated MAXIMUM TTL for stored login state: 10 minutes, matching the server's federation_login_state row lifetime (D-22, CONTRACT.md §12.3 rule 1).
Variables ¶
var ( ErrAuth = errors.New("axiam: authentication error") ErrAuthz = errors.New("axiam: authorization error") ErrNetwork = errors.New("axiam: network error") )
Sentinel errors for errors.Is-based discrimination convenience (CONTRACT.md §2, D-04). These are never returned directly — only *AuthError/*AuthzError/*NetworkError instances are, each of which implements Is(target) to match the corresponding sentinel.
Functions ¶
This section is empty.
Types ¶
type AccessCheck ¶
type AccessCheck struct {
Action string `json:"action"`
ResourceID string `json:"resource_id"`
Scope string `json:"scope,omitempty"`
// SubjectID is optional and, when set, asks the server to evaluate the
// check for this subject rather than the caller's own session
// (CONTRACT.md §11.2 — declarative authorization helpers pass the
// request's authenticated user_id here so the check runs for the end
// user, not the application's own service-account session). Omitted
// from the wire payload when empty, preserving today's request shape
// for CheckAccess/Can/BatchCheck callers that never set it.
SubjectID string `json:"subject_id,omitempty"`
}
AccessCheck is a single access check request (CONTRACT.md §1). ResourceID is a string (server-side UUID) rather than a typed UUID so callers can pass either a UUID string or, in future, other resource-id encodings without a breaking type change; the server is the source of truth for validation.
type AccessResult ¶
AccessResult is the outcome of a single access check (mirrors CheckAccessResponse).
type AuthError ¶
type AuthError struct {
Message string
// Reason is an OPTIONAL stable, machine-readable failure code. It is
// populated for CONTRACT.md §12.4 ID-token validation failures — one of
// invalid_alg, unknown_kid, invalid_signature, invalid_issuer,
// invalid_audience, token_expired, nonce_mismatch (§12 T1 reference
// judgment call 2: the reason code rides on the EXISTING AuthError type
// via this additive field, rather than a second error class) — and left
// "" for every pre-existing AuthError construction site, which is fully
// backward compatible (§12 port addendum item 17).
Reason string
}
AuthError represents an authentication failure: wrong credentials, expired session, MFA failure, or a 401 on refresh (CONTRACT.md §2).
type AuthorizationRequest ¶
type AuthorizationRequest struct {
// URL is the fully-built authorization URL to redirect the browser to.
URL string
// State is a CSPRNG CSRF value (>=128 bits, base64url unpadded) to
// compare against the `state` the IdP returns. Not a secret.
State string
// Nonce is a CSPRNG replay-protection value (>=128 bits) that must equal
// the ID token's `nonce` claim. Not a secret.
Nonce string
// CodeVerifier is the PKCE verifier, secret for its whole lifetime
// (§12.5). Pass it back into OidcExchange.
CodeVerifier Sensitive
}
AuthorizationRequest is the result of OidcBegin — everything the caller needs to start an authorization-code + PKCE login (CONTRACT.md §12.1).
The caller owns this state (§12.3 rule 1). The SDK stores nothing: persist State, Nonce and CodeVerifier in your own HTTP session (or via an OidcStateStore), redirect the browser to URL, and pass Nonce and CodeVerifier back into OidcExchange when the code arrives.
type AuthzError ¶
AuthzError represents an authorization failure: the caller is authenticated but lacks permission for the requested operation (CONTRACT.md §2). Action/ResourceID are optional and populated when known from the response body.
func (*AuthzError) Error ¶
func (e *AuthzError) Error() string
func (*AuthzError) Is ¶
func (e *AuthzError) Is(target error) bool
Is reports whether target is the ErrAuthz sentinel, enabling errors.Is(err, ErrAuthz) to match any *AuthzError.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the AXIAM SDK's REST entry point (CONTRACT.md §1-§10). See NewClient.
func NewClient ¶
NewClient constructs a Client. baseURL and tenantSlug are positional and required (D-03): an empty tenantSlug returns an *AuthError — AXIAM is multi-tenant and there is no default tenant, so this can never be a silent default (CONTRACT.md §5, SC#1).
The returned Client always owns a per-instance cookiejar and a TLS-1.3-minimum transport; WithHTTPClient may override the Transport/timeout, but the SDK re-applies its own jar and TLS config over any supplied client afterward (D-09) so neither can be silently dropped or bypassed.
func (*Client) BatchCheck ¶
func (c *Client) BatchCheck(ctx context.Context, reqs []AccessCheck) ([]AccessResult, error)
BatchCheck performs POST /api/v1/authz/check/batch (CONTRACT.md §1), evaluating an ordered list of checks; results are returned in the same order as reqs. Eligible for CF-01's bounded retry (read-only).
func (*Client) Can ¶
Can is an alias for CheckAccess targeting browser/UI scenarios (CONTRACT.md §1 note) — returns only the allowed boolean.
func (*Client) CheckAccess ¶
func (c *Client) CheckAccess(ctx context.Context, action, resourceID string, scope ...string) (bool, string, error)
CheckAccess performs POST /api/v1/authz/check (CONTRACT.md §1), evaluating a single authorization check for the given action/ resourceID/scope. This is a read-only, idempotent operation eligible for CF-01's bounded retry on transient NetworkError.
func (*Client) CheckAccessAs ¶
func (c *Client) CheckAccessAs(ctx context.Context, subjectID, action, resourceID string, scope ...string) (bool, string, error)
CheckAccessAs performs POST /api/v1/authz/check (CONTRACT.md §1) on behalf of subjectID rather than this Client's own session (CONTRACT.md §11.2). This is additive alongside CheckAccess — existing callers/signatures are unchanged — and exists specifically so declarative authorization helpers (middleware.RequireAccess) can evaluate the check for the request's authenticated user_id instead of the application's own (typically service-account) session. A blank subjectID behaves exactly like CheckAccess (the subject_id field is omitted from the wire request).
func (*Client) Introspect ¶
func (c *Client) Introspect(ctx context.Context, params IntrospectParams) (IntrospectionResult, error)
Introspect performs `POST /oauth2/introspect` (RFC 7662, CONTRACT.md §12.1) — ask the server whether a token is active and, if so, for its metadata.
Requires confidential-client credentials (§12.1 note 4). A 401 here is a CLIENT-CREDENTIAL failure surfaced as *OAuthProtocolError; it never enters the §9 refresh guard, because refreshing the session cannot fix a bad client_secret (§12.3 rule 3) — Introspect never touches Client.guard or the oidc_refresh guard at all.
func (*Client) Login ¶
Login performs POST /api/v1/auth/login (CONTRACT.md §1). On success (no MFA), tokens are already present in the cookie jar and the org_id claim has been resolved+cached. When the server signals MFA is required, returns LoginResult{MFARequired: true, ...} — this is an expected outcome, not an error.
func (*Client) LoginClientCredentials ¶
func (c *Client) LoginClientCredentials(ctx context.Context, params LoginClientCredentialsParams) (OidcTokenSet, error)
LoginClientCredentials performs `POST /oauth2/token` with `grant_type=client_credentials` (CONTRACT.md §12.1) — service-account machine-to-machine login.
Requests no "openid" scope, so the response carries no id_token. Pass params.AdoptAsCredential = true to additionally use the returned access token as this Client's bearer credential for subsequent REST calls (§12.1, a MAY).
Returns *AuthError, client-side with no wire call, when the Client was not constructed with WithOidcClientSecret — this grant cannot be performed by a public client.
func (*Client) Logout ¶
Logout performs POST /api/v1/auth/logout (CONTRACT.md §1) and clears in-memory token state.
func (*Client) OidcBegin ¶
func (c *Client) OidcBegin(configuration OidcConfiguration, params OidcBeginParams) (AuthorizationRequest, error)
OidcBegin builds an authorization request (CONTRACT.md §12.1) — PURE LOCAL COMPUTATION, no network I/O.
Generates a 32-byte CSPRNG State and Nonce (base64url, unpadded) and a fresh PKCE verifier/challenge pair using S256 ONLY — "plain" is not implemented anywhere in this SDK. The URL is built from configuration's AuthorizationEndpoint with exactly the eight parameters §12.1 rule 5 mandates, plus any ExtraParams the caller adds.
Nothing is stored: persist the returned State, Nonce and CodeVerifier yourself (§12.3 rule 1).
Returns a plain (non-taxonomy) error — deliberately NOT *AuthError — when ExtraParams tries to override one of the eight SDK-owned parameters: this is a programming error caught at call time (§12 port addendum item 9).
func (*Client) OidcDiscover ¶
func (c *Client) OidcDiscover(ctx context.Context) (OidcConfiguration, error)
OidcDiscover performs `GET /.well-known/openid-configuration` (CONTRACT.md §12.1) — fetch and cache the OIDC discovery document, with a >=5-minute TTL and single-flight de-duplication of concurrent calls (§12.3 rule 6).
The document's own Issuer is authoritative for ID-token validation and may legitimately differ from the Client's base URL behind a proxy, so a mismatch is never treated as an error.
func (*Client) OidcExchange ¶
func (c *Client) OidcExchange(ctx context.Context, params OidcExchangeParams) (OidcTokenSet, error)
OidcExchange performs `POST /oauth2/token` with `grant_type=authorization_code` (CONTRACT.md §12.1) — exchange an authorization code for a token set, validating the returned ID token in full before returning.
params.Nonce is mandatory: this grant always requests the "openid" scope, so §12.4 rule 6 always applies. If ANY §12.4 rule fails, the whole token set is discarded and *AuthError is raised with the matching Reason code — the access and refresh tokens from the same response are never returned (§12.4 rule 7).
func (*Client) OidcRefresh ¶
func (c *Client) OidcRefresh(ctx context.Context, params OidcRefreshParams) (OidcTokenSet, error)
OidcRefresh performs `POST /oauth2/token` with `grant_type=refresh_token` (CONTRACT.md §12.1) under a single-flight refresh guard (§9): concurrent callers collapse into ONE HTTP request and all receive the same OidcTokenSet (or the same failure), with no retry loop on failure (§9.3).
This is a DISTINCT operation from Client.Refresh, which drives the cookie/opaque-token session path at POST /api/v1/auth/refresh (§5.1). The two are never merged, aliased, or made to fall back to one another (§12.1 "oidc_refresh vs refresh").
This method uses its OWN single-flight guard (oidcState.pendingRefresh) — a SEPARATE instance from the cookie-session Client.guard (internal/refreshguard.Guard). That type's RefreshIfNeeded API compares an "observed" axiam_access cookie value against its own cache, which has no meaning for an OAuth2 refresh_token grant operating on an entirely different, cookie-independent token namespace; reusing the literal same Guard instance for both would corrupt its cookie-session comparison state with an unrelated token stream. A dedicated guard, built from the exact mechanism CONTRACT.md §9 prescribes for Go (a mutex plus a channel carrying the shared result), still satisfies §9's actual requirement for THIS operation — exactly one in-flight refresh, waiters share the outcome, no retry on failure — without that cross-talk. (Documented deviation from the literal wording of the TypeScript reference, which shares one generic mutex-based guard across both operations because its guard has no token-comparison state to corrupt in the first place.)
An id_token in the response is validated against §12.4 rules 1-5 and 7; rule 6 (nonce) is skipped, since OIDC Core §12.2 does not require a nonce in a refresh-issued ID token.
func (*Client) Refresh ¶
Refresh performs POST /api/v1/auth/refresh (CONTRACT.md §1), routed through the sync.Mutex single-flight guard (§9) so concurrent 401s share exactly one in-flight refresh call. A 401 on the refresh call itself is AuthError with no retry (§9.3).
func (*Client) Revoke ¶
func (c *Client) Revoke(ctx context.Context, params RevokeParams) error
Revoke performs `POST /oauth2/revoke` (RFC 7009, CONTRACT.md §12.1) — revoke an access or refresh token.
Per RFC 7009 the server answers 200 for unknown, expired and already-revoked tokens alike, so revocation is IDEMPOTENT: any 2xx is success and no error is raised for a token the server has never seen. Only a 401 (client authentication failed) is an error, surfaced as *OAuthProtocolError (§12.1 note 5, §12.3 rule 3); a 5xx is still a *NetworkError (revoke returning void does not make a server error "success").
Returns *AuthError, client-side with no wire call, when the Client was not constructed with WithOidcClientSecret.
func (*Client) SsoComplete ¶
func (c *Client) SsoComplete(ctx context.Context, params SsoCompleteParams) (SsoCompleteResult, error)
SsoComplete performs `POST /api/v1/auth/federation/oidc/callback` (CONTRACT.md §12.1) — step 2 of upstream SSO: consumes the single-use state, provisions or links the user, and establishes the session.
The session arrives as Set-Cookie, NOT in the response body (§12.1 note 6), so this call goes through the SAME §4 cookie-jar path every other authenticated call already uses — no separate wiring needed. On success the session is marked authenticated via the same absorption Login/VerifyMfa perform (decode the org_id claim, seed the refresh guard), mirroring the TypeScript reference's onAuthenticated() hook (CONTRACT.md §12 T1 judgment call 16).
§12.4 does not apply here — no ID token ever reaches the SDK on the federation path.
func (*Client) SsoStart ¶
func (c *Client) SsoStart(ctx context.Context, params SsoStartParams) (SsoStartResult, error)
SsoStart performs `POST /api/v1/auth/federation/oidc/start` (CONTRACT.md §12.1) — step 1 of first-time SSO against an UPSTREAM IdP. No JWT required.
One tenant form (params.TenantID or params.TenantSlug) and one org form (params.OrgID or params.OrgSlug) must be resolvable, from the arguments or from the Client's own construction options (§5.1) — this Client always has a tenant slug (NewClient requires one), so the tenant form is always resolvable in practice; the organization form still needs WithOrgID/ WithOrgSlug (or an explicit argument) unless the Client already resolved one from a prior Login.
Redirect the browser to the returned AuthorizeURL and round-trip State back into SsoComplete unmodified — the server keeps the nonce to itself (§12.1 note 7).
Returns *AuthError, client-side with no wire call, when tenant or org context cannot be resolved.
type IDTokenClaims ¶
type IDTokenClaims struct {
// Iss is the issuer — matched for exact string equality against the
// discovery document's issuer (rule 3).
Iss string
// Sub is the authenticated end user's stable identifier at AXIAM.
Sub string
// Aud is the audience — contains the relying party's client_id (rule 4).
// May hold one or more values on the wire; always normalized to a slice
// here.
Aud []string
// Exp is the expiry time (epoch seconds).
Exp int64
// Iat is the issued-at time (epoch seconds).
Iat int64
// Nbf is the not-before time (epoch seconds), when the server sends one.
Nbf *int64
// Nonce is the nonce echoed back from the authorization request (rule 6).
Nonce string
// Azp is the authorized party — required to equal client_id when Aud
// holds multiple audiences (rule 4).
Azp string
// Extra preserves any claim not already modeled above (nil when none).
Extra map[string]any
}
IDTokenClaims is the decoded, ALREADY-VALIDATED ID-token claim set carried by OidcTokenSet.IDClaims (CONTRACT.md §12.1).
Claim names are kept verbatim in their JWT/OIDC spelling (Iss, Sub, Aud, ...) rather than Go's usual field-name conventions: they are protocol identifiers a caller cross-references against OIDC Core. Extra preserves any further claim the server sends (e.g. email, preferred_username) — the ID token's full claim set is not enumerated by openapi.json, so unknown claims MUST be preserved and MUST NOT be rejected (§12.1).
type IDTokenFailureReason ¶
type IDTokenFailureReason string
IDTokenFailureReason is one of the seven CONTRACT.md §12.3/§12.4 stable, machine-readable ID-token validation failure codes, carried on the resulting *AuthError's Reason field.
const ( ReasonInvalidAlg IDTokenFailureReason = "invalid_alg" ReasonUnknownKid IDTokenFailureReason = "unknown_kid" ReasonInvalidSignature IDTokenFailureReason = "invalid_signature" ReasonInvalidIssuer IDTokenFailureReason = "invalid_issuer" ReasonInvalidAudience IDTokenFailureReason = "invalid_audience" ReasonTokenExpired IDTokenFailureReason = "token_expired" ReasonNonceMismatch IDTokenFailureReason = "nonce_mismatch" )
The seven §12.4 reason codes, used verbatim (contract-fixed spelling).
type IntrospectParams ¶
type IntrospectParams struct {
// Token is the token to introspect.
Token Sensitive
// TokenTypeHint is an optional RFC 7662 token_type_hint (access_token /
// refresh_token).
TokenTypeHint string
// TenantID is the tenant UUID for the tenant_id query parameter (§12.3
// rule 4).
TenantID string
// Configuration is a pre-fetched discovery document. Fetched via
// OidcDiscover when nil.
Configuration *OidcConfiguration
}
IntrospectParams are the arguments to Introspect (RFC 7662). Requires confidential-client credentials (§12.1 note 4).
type IntrospectionResult ¶
type IntrospectionResult struct {
// Active reports whether the token is currently active.
Active bool
// Sub is the subject the token was issued to.
Sub string
// ClientID is the client the token was issued to.
ClientID string
// Scope is the scope granted to the token.
Scope string
// TokenType is the token type ("Bearer").
TokenType string
// Exp is the expiry time, epoch seconds. Zero when absent.
Exp int64
// Iat is the issued-at time, epoch seconds. Zero when absent.
Iat int64
}
IntrospectionResult is the RFC 7662 introspection result (wire schema IntrospectionResponse). Only Active is guaranteed; the server omits the metadata fields for an inactive token (zero values below).
type JWKSVerifier ¶
JWKSVerifier is the public entry point for this SDK's local JWKS verification primitive (CONTRACT.md §10, D-06) — the shared local-verify mechanism consumed by the net/http middleware (package middleware). It is a thin re-export of the internal jwks.Verifier so callers outside this module never need to import an internal/ package directly.
IMPORTANT: JWKSVerifier.Verify validates the token SIGNATURE ONLY — it does NOT check expiry. Callers using this type directly (rather than via middleware.Middleware, which checks expiry for you) MUST compare the returned Claims.Exp against time.Now().Unix() before trusting the token (WR-03).
func NewJWKSVerifier ¶
NewJWKSVerifier constructs a JWKSVerifier bound to {baseURL}/oauth2/jwks (trailing slash on baseURL trimmed before joining). hc may be nil, in which case a default *http.Client is used. The cache is registered but not eagerly populated; the first Verify call triggers the initial fetch.
This is the exported constructor middleware.Middleware examples wire against — see examples/middleware-guard.
type LoginClientCredentialsParams ¶
type LoginClientCredentialsParams struct {
// Scope is an optional scope to request. This grant requests no "openid"
// scope and the response carries no id_token (§12.1).
Scope string
// TenantID is the tenant UUID for the tenant_id query parameter (§12.3
// rule 4).
TenantID string
// Configuration is a pre-fetched discovery document. Fetched via
// OidcDiscover when nil.
Configuration *OidcConfiguration
// AdoptAsCredential adopts the returned access_token as this Client's
// bearer credential for subsequent REST calls on the same session — the
// §12.1 "login_client_credentials as a credential source" allowance (a
// MAY, hence opt-in and false by default). The token is held behind
// Sensitive and applied only in decorateRequest (never a public field,
// never the cookie jar, never sent to /oauth2/*).
AdoptAsCredential bool
}
LoginClientCredentialsParams are the arguments to LoginClientCredentials (`grant_type=client_credentials`).
type LoginResult ¶
type LoginResult struct {
// MFARequired is true when the server responded with an MFA challenge
// instead of a completed session; call VerifyMfa next with MFAToken.
MFARequired bool
// MFAToken carries the opaque challenge token when MFARequired is
// true. Treated as sensitive (short-lived bearer of "logging in as
// this user").
MFAToken Sensitive
// AvailableMethods lists MFA methods available to satisfy the
// challenge (only populated when MFARequired is true).
AvailableMethods []string
// SessionID is the server-issued session id (only populated on a
// completed, non-MFA-pending login/verify_mfa).
SessionID string
// ExpiresIn is the access token lifetime in seconds, as reported by
// the server (only populated on a completed login/verify_mfa).
ExpiresIn uint64
}
LoginResult is the outcome of Login/VerifyMfa (CF-04). MFA required is an expected outcome, not an error: check MFARequired before assuming the session is established.
type MemoryOidcStateStore ¶
type MemoryOidcStateStore struct {
// contains filtered or unexported fields
}
MemoryOidcStateStore is an in-memory reference implementation of OidcStateStore (CONTRACT.md §12.3 rule 1): per-instance (never process-global), single-use, TTL-bounded. Expired entries are dropped lazily on Save/Consume — there is NO background timer/goroutine, since a library must not keep the host process alive on its own.
Suitable for a single-process app and for tests. A multi-instance deployment needs a shared store (Redis, a database) — implement OidcStateStore directly for that; nothing in this SDK assumes this type.
func NewMemoryOidcStateStore ¶
func NewMemoryOidcStateStore(ttl time.Duration) *MemoryOidcStateStore
NewMemoryOidcStateStore constructs a MemoryOidcStateStore. ttl is the entry lifetime; zero, negative, or greater than OidcStateTTL is CLAMPED to OidcStateTTL (10 minutes) — CONTRACT.md §12.3 rule 1 fixes that as the maximum, while a shorter TTL is honoured verbatim (useful in tests).
func (*MemoryOidcStateStore) Consume ¶
func (s *MemoryOidcStateStore) Consume(state string) (OidcStateEntry, bool)
Consume atomically returns and deletes the entry for state. Deletion happens BEFORE the expiry check, so even an expired hit is removed rather than left to accumulate, and a second call can never return the same entry twice regardless of timing.
func (*MemoryOidcStateStore) Save ¶
func (s *MemoryOidcStateStore) Save(entry OidcStateEntry) error
Save persists entry under its own State, expiring ttl from now.
func (*MemoryOidcStateStore) Size ¶
func (s *MemoryOidcStateStore) Size() int
Size reports the number of unexpired entries currently held. Intended for tests and metrics.
type NetworkError ¶
type NetworkError struct {
Message string
// contains filtered or unexported fields
}
NetworkError represents a transport-level failure: connection refused, timeout, TLS error, DNS failure, or a server-side 5xx (CONTRACT.md §2).
cause is unexported and MUST only ever be populated via newNetworkError, which redacts sensitive headers from any wrapped *http.Response BEFORE constructing the error (D-04, Phase 17 CR-04 carry-forward) — never construct a NetworkError directly from an unredacted *http.Response.
func (*NetworkError) Error ¶
func (e *NetworkError) Error() string
func (*NetworkError) Is ¶
func (e *NetworkError) Is(target error) bool
Is reports whether target is the ErrNetwork sentinel, enabling errors.Is(err, ErrNetwork) to match any *NetworkError.
func (*NetworkError) Unwrap ¶
func (e *NetworkError) Unwrap() error
Unwrap exposes the underlying (already-redacted) cause for errors.Is/As and errors.Unwrap chains.
type OAuthProtocolError ¶
type OAuthProtocolError struct {
AuthError
// ErrorCode is the RFC 6749 "error" field (e.g. invalid_grant).
ErrorCode string
// ErrorDescription is the RFC 6749 "error_description" field.
ErrorDescription string
}
OAuthProtocolError represents an RFC 6749 protocol error returned by an `/oauth2/*` endpoint as an OAuth2ErrorResponse body — `invalid_grant`, `invalid_client`, `invalid_request`, `unsupported_grant_type`, etc. (CONTRACT.md §2, §12.3 rule 3).
It is a language-idiomatic SUB-TYPE of AuthError, not a fourth peer error type (§12 port addendum item 17): OAuthProtocolError embeds AuthError by value and implements Unwrap() returning *AuthError, so:
- errors.Is(err, ErrAuth) matches via the promoted *AuthError.Is method (Is() is checked on err itself before any unwrapping), and
- errors.As(err, &authErrPtr) (with authErrPtr *AuthError) matches by unwrapping once to the embedded AuthError.
Every pre-existing `switch err := err.(type) { case *AuthError: ... }` or `errors.As`/`errors.Is` call site that already handles AuthError keeps working unchanged against an *OAuthProtocolError value — this is precisely what makes contract 1.4 "non-breaking, additive" for Go.
func (*OAuthProtocolError) Unwrap ¶
func (e *OAuthProtocolError) Unwrap() error
Unwrap exposes the embedded AuthError so errors.As(err, &authErrPtr) and errors.Unwrap chains keep matching *AuthError for an *OAuthProtocolError value (see the type doc comment above).
type OidcBeginParams ¶
type OidcBeginParams struct {
// RedirectURI is the relying party's redirect URI, echoed back into
// OidcExchange unchanged.
RedirectURI string
// Scope is the requested scope, space-separated. "openid" is added
// automatically when absent (§12.1 rule 4); the zero value requests
// exactly "openid".
Scope string
// ExtraParams are additional caller-supplied authorization-request query
// parameters (e.g. prompt, login_hint, ui_locales). §12.1 rule 5 allows
// caller-supplied additions but forbids the SDK from adding any of its
// own beyond the mandated eight: attempting to override one of those
// eight is a PROGRAMMING ERROR, returned as a plain error — deliberately
// NOT the AuthError/AuthzError/NetworkError taxonomy (§12 port addendum
// item 9).
ExtraParams map[string]string
}
OidcBeginParams are the arguments to OidcBegin — a pure local computation, no network I/O. ClientID comes from the Client's own configuration (WithOidcClientID), not a per-call argument (§12 T1 judgment call 21).
type OidcConfiguration ¶
type OidcConfiguration struct {
// Issuer is the value an ID token's `iss` claim must equal exactly.
Issuer string `json:"issuer"`
// AuthorizationEndpoint is what OidcBegin builds its redirect URL from.
AuthorizationEndpoint string `json:"authorization_endpoint"`
// TokenEndpoint is used by OidcExchange, OidcRefresh and
// LoginClientCredentials.
TokenEndpoint string `json:"token_endpoint"`
// UserinfoEndpoint is advertised by the server but deliberately NEVER
// called by this SDK (§12.3 rule 5).
UserinfoEndpoint string `json:"userinfo_endpoint"`
// JwksURI is the JWKS document whose keys verify ID-token signatures
// (§12.4 rule 2).
JwksURI string `json:"jwks_uri"`
// RevocationEndpoint is the RFC 7009 endpoint used by Revoke.
RevocationEndpoint string `json:"revocation_endpoint"`
// IntrospectionEndpoint is the RFC 7662 endpoint used by Introspect.
IntrospectionEndpoint string `json:"introspection_endpoint"`
// ResponseTypesSupported lists OAuth2 response_type values the server
// supports.
ResponseTypesSupported []string `json:"response_types_supported"`
// SubjectTypesSupported lists subject identifier types the server
// supports.
SubjectTypesSupported []string `json:"subject_types_supported"`
// IDTokenSigningAlgValuesSupported is informational only: §12.4 rule 1
// pins verification to EdDSA regardless of what appears here.
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
// ScopesSupported lists scopes the server supports.
ScopesSupported []string `json:"scopes_supported"`
// TokenEndpointAuthMethodsSupported lists the client-authentication
// methods the token endpoint supports (client_secret_post, §12.1 note 3).
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
// ClaimsSupported lists claims the server may include in an ID token.
ClaimsSupported []string `json:"claims_supported"`
// GrantTypesSupported lists grant types the token endpoint supports.
GrantTypesSupported []string `json:"grant_types_supported"`
}
OidcConfiguration is the OIDC Discovery 1.0 metadata document served by `GET /.well-known/openid-configuration` (wire schema OidcDiscoveryDocument, CONTRACT.md §12.1). Every field is required by the server's schema.
Issuer is the AUTHORITATIVE issuer for ID-token validation (§12.4 rule 3). It may legitimately differ from the client's base URL when AXIAM runs behind a proxy, so this SDK never rejects a document on an issuer/base-URL mismatch (§12.3 rule 6). Likewise JwksURI is read from here rather than hardcoded.
type OidcExchangeParams ¶
type OidcExchangeParams struct {
// Code is the authorization code the IdP redirected back with.
Code string
// CodeVerifier is the verifier from the matching AuthorizationRequest.
CodeVerifier Sensitive
// RedirectURI is the same redirect_uri that was sent on the
// authorization request.
RedirectURI string
// Nonce is the nonce from the matching AuthorizationRequest. MANDATORY —
// §12.4 rule 6 is not optional for this grant.
Nonce string
// TenantID is the tenant UUID for the token endpoint's required
// tenant_id query parameter. When empty, falls back to the tenant UUID
// resolved from a prior successful Login/Refresh (§12.3 rule 4).
TenantID string
// Configuration is a pre-fetched discovery document, to avoid
// re-reading the (cached) one. Fetched via OidcDiscover when nil.
Configuration *OidcConfiguration
}
OidcExchangeParams are the arguments to OidcExchange (`grant_type=authorization_code`).
type OidcRefreshParams ¶
type OidcRefreshParams struct {
// RefreshToken is the refresh token to redeem.
RefreshToken Sensitive
// Scope is an optional narrowed scope to request. Omitted from the form
// body when empty.
Scope string
// TenantID is the tenant UUID for the tenant_id query parameter (§12.3
// rule 4).
TenantID string
// Configuration is a pre-fetched discovery document. Fetched via
// OidcDiscover when nil.
Configuration *OidcConfiguration
}
OidcRefreshParams are the arguments to OidcRefresh (`grant_type=refresh_token`).
type OidcStateEntry ¶
type OidcStateEntry struct {
// State is the `state` value this entry is keyed by. Not a secret
// (§12.3 rule 2).
State string
// Nonce is checked against the ID token's `nonce` claim. Not a secret
// (§12.3 rule 2).
Nonce string
// CodeVerifier is the PKCE verifier for the matching authorization
// request (§12.5 secret).
CodeVerifier Sensitive
// RedirectURI is the redirect_uri that was sent on the authorization
// request and must be replayed on exchange.
RedirectURI string
// ReturnTo is optional application-owned data, e.g. the page the user
// was heading to before login.
ReturnTo string
}
OidcStateEntry is the tuple an OidcStateStore holds for one in-flight login.
CodeVerifier stays Sensitive while stored (§12.5: the verifier is secret for its whole lifetime, "including ... in any OidcStateStore entry").
type OidcStateStore ¶
type OidcStateStore interface {
// Save persists entry, keyed by its State, starting its TTL now.
Save(entry OidcStateEntry) error
// Consume atomically fetches AND REMOVES the entry for state. ok is
// false when the state is unknown, already consumed, or expired — three
// cases a caller MUST treat identically (as a failed login), because
// distinguishing them leaks whether a state ever existed.
Consume(state string) (entry OidcStateEntry, ok bool)
}
OidcStateStore is an OPTIONAL server-side store for in-flight OidcBegin state (CONTRACT.md §12.3 rule 1).
Implement this to back the login/callback handlers with your own storage (Redis, a database, an encrypted cookie). Two invariants are normative:
- Single-use: Consume MUST return the entry AND delete it atomically, so a replayed callback cannot reuse a state.
- Expiry: an entry older than the store's TTL (10 minutes, at most — OidcStateTTL) MUST NOT be returned.
type OidcTokenSet ¶
type OidcTokenSet struct {
// AccessToken is the OAuth2 access token (§12.5 secret).
AccessToken Sensitive
// TokenType is the token type the server issued ("Bearer").
TokenType string
// ExpiresIn is the access-token lifetime in seconds from the time of the
// response.
ExpiresIn int64
// Scope is the granted scope, when the server narrowed or echoed it.
Scope string
// RefreshToken is the refresh token, when the grant issued one (§12.5
// secret). Empty when absent.
RefreshToken Sensitive
// IDToken is the raw ID token, when the grant issued one (§12.5 secret).
// Empty when absent.
IDToken Sensitive
// IDClaims is the validated ID-token claims — non-nil exactly when
// IDToken is non-empty (§12.1, §12.4).
IDClaims *IDTokenClaims
}
OidcTokenSet is a token set returned by the OAuth2 token endpoint (wire schema TokenResponse), returned by OidcExchange, OidcRefresh and LoginClientCredentials.
AccessToken, RefreshToken and IDToken are Sensitive (§12.5): String()/ fmt/JSON all redact them to "[SENSITIVE]", and the raw value is reachable only through the package-internal expose() accessor. RefreshToken and IDToken are the empty string when the grant did not issue one — no legitimate token is ever the empty string, matching the convention LoginResult.MFAToken already uses.
IDClaims is non-nil exactly when IDToken is non-empty, and holds the ALREADY-VALIDATED claim set (§12.4) — validation happens before this value is ever constructed, so an OidcTokenSet in your hands is never partially trusted (§12.4 rule 7).
type Option ¶
type Option func(*clientConfig)
Option configures a Client at construction time (D-03).
func WithClientCertificate ¶
WithClientCertificate configures a client-certificate identity for mutual TLS (CONTRACT.md §6.1). certPEM is a PEM-encoded X.509 certificate chain and keyPEM is the matching PEM-encoded private key (PKCS#8 or PKCS#1). The SDK presents this identity on BOTH the REST transport (here) and any gRPC channel built for the same logical client (grpc.NewTLSCredentials).
Presenting a client certificate NEVER relaxes server verification: this is additive to WithCustomCA/§6 and keeps the SDK's TLS-1.3 floor and strict RootCAs behavior unchanged. A non-PEM cert/key pair is a construction-time error returned from NewClient, consistent with WithCustomCA.
The private key is secret material (§7): it is held behind the SDK's Sensitive type and never appears in any log, error, or display output.
func WithCustomCA ¶
WithCustomCA adds a PEM-encoded CA certificate to the TLS verification chain (§6). This is the ONLY TLS-related escape hatch — there is no option anywhere in this SDK that disables or weakens certificate verification. Returns a construction-time error via NewClient if pem is not valid PEM.
func WithHTTPClient ¶
WithHTTPClient supplies a base *http.Client whose Transport/Timeout the SDK adopts. D-09: the SDK ALWAYS re-applies its own cookiejar and TLS config over the supplied client afterward — an override can never silently drop the jar (breaking every post-login request) or bypass TLS verification.
func WithLogger ¶
WithLogger supplies an injectable, redaction-aware logger (CF-02). OFF by default (nil logger — the SDK never logs unless a logger is supplied). The SDK never emits raw token values regardless of the logger's configured level (Sensitive redacts itself in any log call).
func WithOidcClientID ¶
WithOidcClientID sets the relying party's OAuth2 client_id (CONTRACT.md §12.1), used on every §12 grant and matched against the ID token's aud/azp (§12.4 rule 4). Required before calling any §12 operation other than OidcDiscover.
func WithOidcClientSecret ¶
WithOidcClientSecret configures a confidential client's client_secret (CONTRACT.md §12.1), held behind Sensitive (§12.5). Omit for a public client: LoginClientCredentials, Introspect and Revoke then return an *AuthError client-side, without a wire call (§12.1 note 4 — a public client cannot call them).
func WithOidcClockSkew ¶
WithOidcClockSkew overrides the permitted ID-token clock skew, in seconds. Clamped to [1, MaxIDTokenClockSkewSec] (60s) per CONTRACT.md §12.4 rule 5 — the contract forbids configuring it above that bound.
func WithOidcDiscoveryTTL ¶
WithOidcDiscoveryTTL overrides the OIDC discovery-document cache TTL. Floored at MinOidcDiscoveryTTL (5 minutes) per CONTRACT.md §12.3 rule 6 — a smaller configured value is silently raised to the floor.
func WithOrgID ¶
WithOrgID sets the organization UUID the real login/refresh endpoints require (RESEARCH.md Pitfall 3). Mutually exclusive with WithOrgSlug — last call wins.
func WithOrgSlug ¶
WithOrgSlug sets the organization slug the real login/refresh endpoints require (RESEARCH.md Pitfall 3). Mutually exclusive with WithOrgID — last call wins.
func WithTimeout ¶
WithTimeout overrides the default request timeout applied to the SDK's http.Client (CF-03; default 30s).
type RevokeParams ¶
type RevokeParams struct {
// Token is the token to revoke.
Token Sensitive
// TokenTypeHint is an optional RFC 7009 token_type_hint.
TokenTypeHint string
// TenantID is the tenant UUID for the tenant_id query parameter (§12.3
// rule 4).
TenantID string
// Configuration is a pre-fetched discovery document. Fetched via
// OidcDiscover when nil.
Configuration *OidcConfiguration
}
RevokeParams are the arguments to Revoke (RFC 7009). Requires confidential-client credentials (§12.1 note 4).
type Sensitive ¶
type Sensitive string
Sensitive wraps a token-carrying string so it can never accidentally leak via fmt verbs, Go-syntax representation, or JSON encoding (CONTRACT.md §7, D-08). All token-carrying fields (access token, refresh token, MFA challenge token, AMQP signing key) MUST use this type.
The raw value is reachable only via the package-internal expose() accessor — Sensitive deliberately has no public getter.
func (Sensitive) Format ¶
Format implements fmt.Formatter, closing the fmt-verb leak path (%v/%+v/%s/%q/width/precision) that a bare String() method does not fully cover — this is the CR-04 leak class this type exists to prevent.
func (Sensitive) GoString ¶
GoString implements fmt.GoStringer, covering %#v (Go-syntax representation), which bypasses String()/Format() entirely if not implemented.
func (Sensitive) MarshalJSON ¶
MarshalJSON implements json.Marshaler so any struct embedding a Sensitive field serializes the redacted placeholder rather than the raw value.
type SsoCompleteParams ¶
type SsoCompleteParams struct {
// State is the `state` value the IdP redirected back with — must be the
// one SsoStart returned.
State string
// Code is the authorization code the IdP redirected back with.
Code string
}
SsoCompleteParams are the arguments to SsoComplete (`POST /api/v1/auth/federation/oidc/callback`).
type SsoCompleteResult ¶
type SsoCompleteResult struct {
// UserID is the provisioned/linked user's UUID.
UserID string
// SessionID is the established session's UUID.
SessionID string
// ExpiresIn is the session/access-token lifetime in seconds.
ExpiresIn int64
// RedirectURI is the post-login destination that was stored during
// SsoStart.
RedirectURI string
}
SsoCompleteResult is the result of SsoComplete (wire schema SsoLoginSuccessResponse).
It carries NO token material — the session arrives as Set-Cookie, so the §4 cookie jar (already owned by every *Client) is what actually captures it (§12.1 note 6).
type SsoStartParams ¶
type SsoStartParams struct {
// FederationConfigID is the UUID of the server-side federation
// configuration identifying the upstream IdP.
FederationConfigID string
// RedirectURI is the post-login destination, stored server-side and
// echoed back by SsoComplete.
RedirectURI string
// TenantID is the tenant UUID. Defaults to the Client's own tenant when
// empty (this SDK always constructs a Client with a tenant slug, so the
// default tenant form is TenantSlug — see TenantSlug below).
TenantID string
// TenantSlug is the tenant slug — the tenant form used by default,
// since NewClient always requires one.
TenantSlug string
// OrgID is the organization UUID. Defaults to the Client's configured
// organization (WithOrgID) when empty.
OrgID string
// OrgSlug is the organization slug. Defaults to the Client's configured
// organization (WithOrgSlug) when empty.
OrgSlug string
}
SsoStartParams are the arguments to SsoStart (`POST /api/v1/auth/federation/oidc/start`).
One tenant form (TenantID or TenantSlug) and one org form (OrgID or OrgSlug) must be resolvable, from these fields or from the Client's own construction options (CONTRACT.md §5.1).
type SsoStartResult ¶
type SsoStartResult struct {
// AuthorizeURL is the upstream IdP authorization URL to redirect the
// browser to.
AuthorizeURL string
// State is the single-use CSRF state to round-trip back into SsoComplete
// unmodified.
State string
// ExpiresInSecs is the remaining TTL of the server-side state row, in
// seconds (600 = 10 min).
ExpiresInSecs int64
}
SsoStartResult is the result of SsoStart (wire schema OidcStartResponse).
There is deliberately no nonce: on the federation path the nonce never leaves the server (§12.1 note 7). Round-trip State into SsoComplete unmodified — the server stores it single-use with a 10-minute TTL and recovers the whole login context from it.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package amqp implements the AXIAM AMQP event consumer: a closure-handler Consume loop that HMAC-SHA256-verifies every delivery BEFORE the caller's handler ever runs (CONTRACT.md §8, D-07, SC#4).
|
Package amqp implements the AXIAM AMQP event consumer: a closure-handler Consume loop that HMAC-SHA256-verifies every delivery BEFORE the caller's handler ever runs (CONTRACT.md §8, D-07, SC#4). |
|
examples
|
|
|
amqp-consumer
command
Command amqp-consumer demonstrates amqp.Consume with a closure handler that shows the full ack/nack matrix (CONTRACT.md §8, D-07).
|
Command amqp-consumer demonstrates amqp.Consume with a closure handler that shows the full ack/nack matrix (CONTRACT.md §8, D-07). |
|
authz-check
command
Command authz-check demonstrates the REST authorization surface: CheckAccess, Can (the browser/UI alias), and BatchCheck (CONTRACT.md §1).
|
Command authz-check demonstrates the REST authorization surface: CheckAccess, Can (the browser/UI alias), and BatchCheck (CONTRACT.md §1). |
|
grpc-checkaccess
command
Command grpc-checkaccess demonstrates the gRPC authorization transport: CheckAccess and BatchCheck over a lazily-connected *grpc.ClientConn (CONTRACT.md §1, §5, §9).
|
Command grpc-checkaccess demonstrates the gRPC authorization transport: CheckAccess and BatchCheck over a lazily-connected *grpc.ClientConn (CONTRACT.md §1, §5, §9). |
|
login-mfa
command
Command login-mfa demonstrates the two-phase Login/VerifyMfa flow (CONTRACT.md §1, §5).
|
Command login-mfa demonstrates the two-phase Login/VerifyMfa flow (CONTRACT.md §1, §5). |
|
middleware-guard
command
Command middleware-guard demonstrates wrapping a sample net/http route with middleware.Middleware (CONTRACT.md §10, SC#1), plus a second route additionally protected with middleware.RequireAccess (CONTRACT.md §11 declarative authorization helpers).
|
Command middleware-guard demonstrates wrapping a sample net/http route with middleware.Middleware (CONTRACT.md §10, SC#1), plus a second route additionally protected with middleware.RequireAccess (CONTRACT.md §11 declarative authorization helpers). |
|
oidc-login
command
Command oidc-login demonstrates "Login with AXIAM" — the OIDC/SSO relying-party helpers (CONTRACT.md §12) — wired into a plain net/http server via middleware.OidcLoginHandler and middleware.OidcCallbackHandler.
|
Command oidc-login demonstrates "Login with AXIAM" — the OIDC/SSO relying-party helpers (CONTRACT.md §12) — wired into a plain net/http server via middleware.OidcLoginHandler and middleware.OidcCallbackHandler. |
|
Package grpc implements the gRPC transport for AuthorizationService (CheckAccess/BatchCheckAccess) with strict TLS and a sync-safe auth/tenant interceptor (CONTRACT.md §5/§6, SC#3).
|
Package grpc implements the gRPC transport for AuthorizationService (CheckAccess/BatchCheckAccess) with strict TLS and a sync-safe auth/tenant interceptor (CONTRACT.md §5/§6, SC#3). |
|
internal
|
|
|
jwks
Package jwks implements local JWKS fetch/cache/verification via lestrrat-go/jwx/v3 (D-06/§10), the shared local-verify primitive consumed by the net/http middleware (Plan 05) and any proactive-refresh check.
|
Package jwks implements local JWKS fetch/cache/verification via lestrrat-go/jwx/v3 (D-06/§10), the shared local-verify primitive consumed by the net/http middleware (Plan 05) and any proactive-refresh check. |
|
refreshguard
Package refreshguard implements the sync.Mutex single-flight refresh guard required by CONTRACT.md §9: exactly one in-flight POST /api/v1/auth/refresh call across any number of concurrent callers observing the same expired access token, with a double-check-after-lock pattern and no retry loop on failure (§9.3).
|
Package refreshguard implements the sync.Mutex single-flight refresh guard required by CONTRACT.md §9: exactly one in-flight POST /api/v1/auth/refresh call across any number of concurrent callers observing the same expired access token, with a double-check-after-lock pattern and no retry loop on failure (§9.3). |
|
Package middleware implements the net/http middleware / route-guard interface (CONTRACT.md §10, D-06).
|
Package middleware implements the net/http middleware / route-guard interface (CONTRACT.md §10, D-06). |