axiam

package module
v1.0.0-alpha21 Latest Latest
Warning

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

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

README

axiam SDK (Go)

CI Coverage Status Go Reference Go Report Card License

Official Go client SDK for AXIAM — Access eXtended Identity and Authorization Management.

Package identity

Contract conformance

This SDK conforms to CONTRACT.md §1–§12 (including §6.1 mTLS).

See CONTRACT.md for the full cross-language behavioral contract.

Status

Implemented (Phase 18). REST client (login/MFA/refresh/logout, authz check/can/batch-check), gRPC client (authz check/batch-check plus GetUserInfo), AMQP consumer with HMAC verification, local JWKS verification, net/http middleware, and OIDC/SSO relying-party helpers (§12 — "Login with AXIAM") are all available. Six runnable examples live under examples/.

Installation

go get github.com/ilpanich/axiam-go-sdk@latest

Or pin an explicit release:

go get github.com/ilpanich/axiam-go-sdk@vX.Y.Z
import axiam "github.com/ilpanich/axiam-go-sdk"

Usage

Login + MFA (§1, §5)
// tenantSlug is required — no default tenant (§5). Login and Refresh also
// require organization context (§5.1) — a tenant slug is only unique within
// an organization — so pass the org via WithOrgSlug (or WithOrgID for a UUID);
// a login without it is rejected with 400 "must provide org_id or org_slug".
client, err := axiam.NewClient(baseURL, tenantSlug, axiam.WithOrgSlug(orgSlug))
if err != nil {
	// handle error
}

result, err := client.Login(ctx, email, password)
if err != nil {
	// handle error
}
if result.MFARequired {
	completed, err := client.VerifyMfa(ctx, result.MFAToken, totpCode)
	// ...
}

See examples/login-mfa.

REST authorization checks — CheckAccess / Can / BatchCheck (§1)
allowed, reason, err := client.CheckAccess(ctx, "resource:read", resourceID)
canWrite, err := client.Can(ctx, "resource:write", resourceID)
results, err := client.BatchCheck(ctx, []axiam.AccessCheck{
	{Action: "resource:read", ResourceID: resourceID},
})

See examples/authz-check.

gRPC authorization checks (§1, §5, §9)
creds, err := axiamgrpc.NewTLSCredentials(nil, nil, nil) // strict TLS; arg 1 is an optional custom CA PEM for dev servers (§6)
conn, err := axiamgrpc.NewGRPCClient(target, creds, interceptor)
authzClient := axiamgrpc.NewAuthzClient(conn, refreshFn)

allowed, denyReason, err := authzClient.CheckAccess(ctx, axiamgrpc.CheckAccessRequest{
	TenantID: tenantID, SubjectID: subjectID, Action: "resource:read", ResourceID: resourceID,
})

See examples/grpc-checkaccess.

gRPC userinfo — GetUserInfo (§1.1)

GetUserInfo is the low-latency gRPC counterpart of the server's REST GET /oauth2/userinfo endpoint (CONTRACT.md §1.1). It invokes axiam.v1.UserInfoService/GetUserInfo on the same gRPC channel and shares the same auth + x-tenant-id interceptor as CheckAccess; the request is empty (identity is derived server-side from the bearer token) and it drives the same single-flight refresh + one-shot retry on UNAUTHENTICATED (§9).

userInfoClient := axiamgrpc.NewUserInfoClient(conn, refreshFn) // same conn as NewAuthzClient

info, err := userInfoClient.GetUserInfo(ctx)
// info.Sub, info.TenantID, info.OrgID are always present.
// info.Email / info.PreferredUsername are *string — non-nil only when the
// access token carries the "email" / "profile" scope respectively.

See examples/grpc-checkaccess.

mTLS / client certificates (§6.1)

AXIAM can authenticate IoT devices and service accounts by mutual TLS: the client presents an X.509 identity certificate (signed by the tenant's organization CA) that the server binds to a service account. Configure the client identity with WithClientCertificate — it is applied to both the REST and gRPC transports of the same logical client, and it never relaxes server verification (it is additive to WithCustomCA/§6, and the TLS-1.3 floor and strict RootCAs behavior are unchanged).

// PEM cert chain + PEM private key (PKCS#8 or PKCS#1).
client, err := axiam.NewClient(baseURL, tenantSlug,
	axiam.WithCustomCA(serverCAPEM),                 // trust the server's CA (§6)
	axiam.WithClientCertificate(certPEM, keyPEM),    // present our identity (§6.1)
)

// The same identity over gRPC — pass the SAME cert chain + key:
creds, err := axiamgrpc.NewTLSCredentials(serverCAPEM, certPEM, keyPEM)

mTLS is opt-in: omitting WithClientCertificate leaves the default bearer-cookie behavior unchanged. 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, and there is no public getter for it.

AMQP consumer with HMAC verification (§8)
handler := func(ctx context.Context, event amqp.Event) error {
	// process event.Fields — hmac_signature has already been verified and removed
	return nil // Ack; return amqp.ErrDrop for a poison message (Nack, no requeue)
}
err := amqp.Consume(ctx, ch, queue, signingKey, handler)

See examples/amqp-consumer.

net/http middleware (§10)
verifier, err := axiam.NewJWKSVerifier(ctx, baseURL, nil)
guarded := middleware.Middleware(verifier, tenantSlug)(mux)

// inside a handler:
user, ok := middleware.UserFromContext(r.Context())

See examples/middleware-guard.

Declarative authorization helpers (§11)

On top of the §10 Middleware guard, middleware.RequireAuth, middleware.RequireAccess, and middleware.RequireRole add a per-route authorization layer (CONTRACT.md §11). Go has no macro/annotation/decorator facility, so these are per-route http.Handler wrappers under the same canonical require_auth / require_access / require_role vocabulary every other AXIAM SDK uses. They run strictly after the §10 guard — they never extract or verify a token themselves, only consuming the identity Middleware already injected — and they perform no decision caching: every request re-checks.

verifier, err := axiam.NewJWKSVerifier(ctx, baseURL, nil)
client, err := axiam.NewClient(baseURL, tenantSlug) // *axiam.Client satisfies middleware.AccessChecker

mux := http.NewServeMux()

// GET /docs/{id} requires the authenticated caller to pass a
// "documents:read" check for the {id} resolved from the path.
mux.Handle("/docs/{id}", middleware.RequireAccess(
	client, "documents:read", middleware.ResourceFromPath("id"),
)(docHandler))

// A route that only needs an authenticated identity, no resource check.
mux.Handle("/whoami", middleware.RequireAuth()(whoamiHandler))

// A cheap, LOCAL role check — no server round-trip, and NOT a substitute
// for RequireAccess's resource-level check.
mux.Handle("/admin", middleware.RequireRole("admin")(adminHandler))

guarded := middleware.Middleware(verifier, tenantSlug)(mux) // §10 guard wraps the whole mux

The check is always made for the request's authenticated user (subject_id), never the application's own client session — this is why RequireAccess takes a middleware.AccessChecker (satisfied by *axiam.Client's additive CheckAccessAs method) rather than reusing CheckAccess directly. A resource id that can't be resolved (missing path value, empty StaticResource, or a failing custom ResourceResolver) is a 400, never a silent allow. A transport failure while calling the authz endpoint fails closed with 503 — it is never treated as an allow.

See examples/middleware-guard (the GET /docs/{id} route).

OIDC / SSO relying-party helpers — "Login with AXIAM" (§12)

*axiam.Client exposes the nine canonical CONTRACT.md §12 operations for building an OIDC relying party against AXIAM's own OIDC provider, driving its client_credentials service-account grant, introspecting/revoking tokens, and stepping through the upstream-IdP federation endpoints:

Operation Wire call Purpose
OidcDiscover(ctx) GET /.well-known/openid-configuration Fetch and cache the discovery document (≥5 min TTL, single-flight per client).
OidcBegin(configuration, params) (none — pure local computation) Build the authorization URL with a CSPRNG state/nonce and an S256 PKCE challenge.
OidcExchange(ctx, params) POST /oauth2/token (authorization_code) Exchange a code for a token set, validating the ID token in full.
OidcRefresh(ctx, params) POST /oauth2/token (refresh_token) Refresh an OidcTokenSet under a single-flight guard.
LoginClientCredentials(ctx, params) POST /oauth2/token (client_credentials) Service-account machine-to-machine login.
Introspect(ctx, params) POST /oauth2/introspect RFC 7662 token introspection.
Revoke(ctx, params) POST /oauth2/revoke RFC 7009 token revocation (idempotent).
SsoStart(ctx, params) POST /api/v1/auth/federation/oidc/start Step 1 of upstream-IdP SSO.
SsoComplete(ctx, params) POST /api/v1/auth/federation/oidc/callback Step 2: establishes the session via Set-Cookie.

Configure the relying party's client credentials at construction time — client_id is needed for every grant and for §12.4's audience check, so it lives on the Client, never a per-call argument:

client, err := axiam.NewClient(baseURL, tenantSlug,
	axiam.WithOidcClientID("my-app"),
	axiam.WithOidcClientSecret(clientSecret), // omit for a public client
)

The caller owns the login state — the SDK stores nothing. OidcBegin returns AuthorizationRequest{URL, State, Nonce, CodeVerifier} and touches no store; persist State, Nonce and CodeVerifier in your own session (or via the optional axiam.OidcStateStore / axiam.NewMemoryOidcStateStore) and pass Nonce + CodeVerifier back into OidcExchange yourself:

configuration, err := client.OidcDiscover(ctx)
request, err := client.OidcBegin(configuration, axiam.OidcBeginParams{
	RedirectURI: redirectURI,
	Scope:       "openid profile email",
})
// ...persist request.State / request.Nonce / request.CodeVerifier, then...
http.Redirect(w, r, request.URL, http.StatusFound)

// on the callback, after checking the returned `state` matches:
tokens, err := client.OidcExchange(ctx, axiam.OidcExchangeParams{
	Code: code, CodeVerifier: request.CodeVerifier, Nonce: request.Nonce,
	RedirectURI: redirectURI, TenantID: tenantID,
})
fmt.Println(tokens.IDClaims.Sub) // the validated ID-token subject

middleware.OidcLoginHandler / middleware.OidcCallbackHandler wrap that same sequence as two http.Handlers, using an axiam.OidcStateStore to bridge the login and callback requests:

store := axiam.NewMemoryOidcStateStore(0) // 10-minute TTL, single-use consume
opts := middleware.OidcLoginOptions{
	Client: client, Store: store, RedirectURI: redirectURI, Scope: "openid profile",
	OnSuccess: func(w http.ResponseWriter, r *http.Request, tokens axiam.OidcTokenSet, entry axiam.OidcStateEntry) {
		// establish YOUR OWN application session here — the SDK never does.
	},
}
mux.Handle("/login", middleware.OidcLoginHandler(opts))
mux.Handle("/auth/callback", middleware.OidcCallbackHandler(opts))

access_token, refresh_token, id_token, client_secret and code_verifier are all Sensitive (§7/§12.5) — including while a code_verifier sits inside an AuthorizationRequest or an OidcStateStore entry. state and nonce are not secrets and are plain strings. PKCE is S256-only: plain is not implemented anywhere in this SDK. OidcRefresh runs under its own single-flight guard so concurrent callers share one wire call; OidcExchange/OidcRefresh validate any id_token against the full CONTRACT.md §12.4 checklist (EdDSA-only, issuer/audience/time/nonce) and discard the WHOLE token set — access and refresh token included — on any failure. An OAuth2ErrorResponse from /oauth2/* surfaces as *axiam.OAuthProtocolError, a sub-type of *axiam.AuthError (existing errors.Is(err, axiam.ErrAuth) / errors.As(err, &authErr) handling keeps matching it unchanged); a 401 from Introspect/Revoke never enters the §9 refresh guard.

See examples/oidc-login.

Versioning

Releases are tagged vX.Y.Z. Pushing such a tag triggers the module-publish CI job, which verifies the tag was cut from main and asks proxy.golang.org to fetch it; pull-request events never trigger publish.

There is no registry upload step — for Go, the git tag is the release, and go get resolves it through the module proxy. API docs appear automatically on pkg.go.dev once the proxy has seen the tag.

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

View Source
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.

View Source
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.

View Source
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

View Source
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

type AccessResult struct {
	Allowed bool   `json:"allowed"`
	Reason  string `json:"reason,omitempty"`
}

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).

func (*AuthError) Error

func (e *AuthError) Error() string

func (*AuthError) Is

func (e *AuthError) Is(target error) bool

Is reports whether target is the ErrAuth sentinel, enabling errors.Is(err, ErrAuth) to match any *AuthError.

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

type AuthzError struct {
	Message    string
	Action     string
	ResourceID string
}

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

func NewClient(baseURL, tenantSlug string, opts ...Option) (*Client, error)

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

func (c *Client) Can(ctx context.Context, action, resourceID string, scope ...string) (bool, error)

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

func (c *Client) Login(ctx context.Context, email, password string) (LoginResult, error)

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

func (c *Client) Logout(ctx context.Context) error

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

func (c *Client) Refresh(ctx context.Context) error

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.

func (*Client) VerifyMfa

func (c *Client) VerifyMfa(ctx context.Context, mfaToken Sensitive, code string) (LoginResult, error)

VerifyMfa performs POST /api/v1/auth/mfa/verify (CONTRACT.md §1), completing the two-phase flow started by Login when MFARequired was true.

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

type JWKSVerifier = jwks.Verifier

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

func NewJWKSVerifier(ctx context.Context, baseURL string, hc *http.Client) (*JWKSVerifier, error)

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:

  1. Single-use: Consume MUST return the entry AND delete it atomically, so a replayed callback cannot reuse a state.
  2. 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

func WithClientCertificate(certPEM, keyPEM []byte) Option

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

func WithCustomCA(pem []byte) Option

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

func WithHTTPClient(hc *http.Client) Option

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

func WithLogger(logger *slog.Logger) Option

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

func WithOidcClientID(clientID string) Option

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

func WithOidcClientSecret(clientSecret string) Option

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

func WithOidcClockSkew(seconds int) Option

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

func WithOidcDiscoveryTTL(ttl time.Duration) Option

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

func WithOrgID(id uuid.UUID) Option

WithOrgID sets the organization UUID the real login/refresh endpoints require (RESEARCH.md Pitfall 3). Mutually exclusive with WithOrgSlug — last call wins.

func WithOrgSlug

func WithOrgSlug(slug string) Option

WithOrgSlug sets the organization slug the real login/refresh endpoints require (RESEARCH.md Pitfall 3). Mutually exclusive with WithOrgID — last call wins.

func WithTimeout

func WithTimeout(d time.Duration) Option

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

func (Sensitive) Format(f fmt.State, verb rune)

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

func (Sensitive) GoString() string

GoString implements fmt.GoStringer, covering %#v (Go-syntax representation), which bypasses String()/Format() entirely if not implemented.

func (Sensitive) MarshalJSON

func (Sensitive) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler so any struct embedding a Sensitive field serializes the redacted placeholder rather than the raw value.

func (Sensitive) String

func (Sensitive) String() string

String implements fmt.Stringer. Covers direct String() calls and the default fmt verb behavior for types without a more specific Format/GoString override (String() alone would still leak on %#v without GoString below).

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.

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).

Jump to

Keyboard shortcuts

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