Backends and MFA
All optional typed backend interfaces on this page are connected to current production IdP, API, backchannel, gRPC, or WebAuthn flows. They are not placeholder declarations. If a selected backend does not implement the interface required by an operation, the host maps it to an unknown/unsupported backend outcome. Returned errors and recovered panics become temporary backend failures.
Password and account backend
BackendAuthRequest
type BackendAuthRequest struct {
Snapshot RequestSnapshot
Runtime RuntimeContext
Credentials CredentialProvider
Username string
}
Snapshot and Runtime describe the current authentication request. Credentials supplies the password only when the module obtained CapabilityCredentials; a password backend should require that capability during registration. Username is the host-selected lookup/login string and can differ from later BackendResult.Account.
AccountListRequest
type AccountListRequest struct {
Snapshot RequestSnapshot
Runtime RuntimeContext
Username string
}
Carries request state and the account-list lookup subject. It intentionally has no credential provider.
AccountListResult
type AccountListResult struct {
Status *StatusMessage
Accounts []string
Facts []PolicyFact
}
Accounts is the returned account set/list. Status text/key are applied and Status.Temporary can force temporary failure. Facts are recorded, but current account-list adapter validation is weaker than source fact validation: it normalizes non-empty IDs and compatible values without equivalent registry/stage/type enforcement.
Backend
type Backend interface {
Name() string
VerifyPassword(context.Context, BackendAuthRequest) (BackendResult, error)
ListAccounts(context.Context, AccountListRequest) (AccountListResult, error)
}
Name must be a unique valid local component name. VerifyPassword returns the complete backend result. ListAccounts implements account-provider operations. Both run synchronously, are panic-protected, and must honor cancellation. The host invokes only registered backends.
PositivePasswordCacheBackend
type PositivePasswordCacheBackend interface {
PositivePasswordCacheable() bool
}
Optional opt-in for the host's Redis positive password cache. The host uses it only when the operator also sets plugins.modules[].positive_password_cache: true; registration rejects that setting if any backend of the module lacks the declaration or returns false. Backends without the declaration keep their current behavior.
The declaration is a semantic promise the host cannot prove for trusted in-process code: successful results depend only on credentials and identity, stay valid for storage.redis.positive_cache_ttl, and skipping VerifyPassword and its side effects on a cache hit is acceptable. Do not opt in for one-time credentials, request- or IP-dependent verification, or mandatory per-login database writes; put repeatable per-request work into subject sources or post-actions instead.
The cache stores an immutable JSON snapshot of the original BackendResult before subject processing, including Account, AccountField, attributes, identity field names, groups and group DNs, BackendServer, status, and facts. It is restored through the live backend adapter, so the qualified backend name module.component and the backend reference are reconstructed identically. Only successful found-user results that survive an exact JSON encode/decode round trip are stored: facts with integer types, float32, typed containers, or other values whose Go type changes in JSON make the whole result uncacheable, and such requests continue through normal authentication without a cache write. Strings, booleans, finite float64 values, and JSON-native containers round-trip.
The payload is versioned and bound to the submitted username, protocol, and OIDC client ID. A cache hit enters the normal password pipeline as frozen host credential evidence; subject patches cannot raise a failed credential into a permit or a cache write. Subject sources, Policy evaluation, and post-actions still run on every request, and their changes are not part of the snapshot. Wrong passwords miss the cache; no negative entries are stored. The process-local positive backend cache is separate and already serves plugin results.
PositivePasswordCacheScopeBackend
type PositivePasswordCacheScopeBackend interface {
PositivePasswordCacheScope(RequestSnapshot) (scope string, cacheable bool)
}
Implement this as well when a backend resolves short usernames with an additional identity input, such as a tenant or domain header. Return the effective domain, or a deterministic discriminator that contains every additional input that can change the result, and false when the request cannot be cached safely; false bypasses both the Redis and the in-memory positive cache for that request. The method must be pure: no I/O, no side effects, no secrets in the scope. The host calls it behind the plugin panic boundary, and a panic bypasses the cache.
The canonical account remains the Redis key; the envelope additionally binds username, protocol, OIDC client, and plugin scope, and a different scope requires a fresh backend lookup before any cached identity is applied. The same scope check guards the in-memory cache, and scope-aware plugins without the joint operator/plugin opt-in bypass the in-memory cache too. Derive the scope with the same resolver VerifyPassword uses, including fallbacks and fully qualified username precedence. The account-mapping index is not scoped: alternating domains for one short username can cause extra misses but never authorize the wrong canonical account. A scope does not make time-dependent decisions cacheable.
Operator configuration and invalidation are described in Positive password cache for backend modules.
TOTP
Request and result structs
| Type and fields | Meaning |
|---|---|
TOTPBeginRequest { Snapshot, Runtime, IdempotencyKey, Username } | Starts registration. IdempotencyKey lets the backend make retries safe; Username is the subject. |
TOTPBeginResult { Status, BackendServer, ExpiresAt, PendingRegistrationID, OTPAuthURL } | Returns status, serving backend, expiry, opaque pending ID, and otpauth: enrollment URL. Treat the URL and pending ID as secrets. |
TOTPFinishRequest { Snapshot, Runtime, IdempotencyKey, Username, PendingRegistrationID, Code } | Completes registration with the opaque pending ID and user code. Code is secret authentication material. |
TOTPFinishResult { Status, BackendServer, Verified } | Reports serving backend and whether completion/code verification succeeded. |
TOTPVerifyRequest { Snapshot, Runtime, Username, Code } | Verifies one TOTP code. |
TOTPVerifyResult { Status, BackendServer, Verified } | Reports serving backend and verification outcome. |
TOTPDeleteRequest { Snapshot, Runtime, IdempotencyKey, Username } | Deletes TOTP state idempotently. |
ExpiresAt is a time.Time; zero means no declared expiry. All Snapshot and Runtime fields have the common semantics. Current typed-MFA adapters apply status text/key but ignore Status.Temporary.
TOTPBackend
type TOTPBackend interface {
BeginTOTP(context.Context, TOTPBeginRequest) (TOTPBeginResult, error)
FinishTOTP(context.Context, TOTPFinishRequest) (TOTPFinishResult, error)
VerifyTOTP(context.Context, TOTPVerifyRequest) (TOTPVerifyResult, error)
DeleteTOTP(context.Context, TOTPDeleteRequest) error
}
Implement this optional interface on the same object registered as Backend. The host routes TOTP operations to it. Writes on selected-edge/non-owning paths can be rejected by the host; preserve idempotency and backend affinity using the supplied key and returned server ref.
Recovery codes
Request and result structs
| Type and fields | Meaning |
|---|---|
RecoveryCodeGenerateRequest { Snapshot, Runtime, IdempotencyKey, Username, Count } | Requests Count new recovery codes. Enforce a safe backend-specific non-zero maximum. |
RecoveryCodeGenerateResult { Status, BackendServer, Codes } | Returns serving backend and newly generated one-time codes. Codes are credentials and must be shown/stored only through the intended secure flow. |
RecoveryCodeUseRequest { Snapshot, Runtime, IdempotencyKey, Username, Code } | Atomically attempts to consume one code. |
RecoveryCodeUseResult { Status, BackendServer, Valid, Remaining } | Reports whether consumption succeeded and remaining count. Use non-negative Remaining; no additional public validation guarantees it. |
RecoveryCodeDeleteRequest { Snapshot, Runtime, IdempotencyKey, Username } | Idempotently removes all recovery codes. |
All named fields are public struct fields. Current typed-MFA status handling applies text/key and ignores Status.Temporary.
RecoveryCodeBackend
type RecoveryCodeBackend interface {
GenerateRecoveryCodes(context.Context, RecoveryCodeGenerateRequest) (RecoveryCodeGenerateResult, error)
UseRecoveryCode(context.Context, RecoveryCodeUseRequest) (RecoveryCodeUseResult, error)
DeleteRecoveryCodes(context.Context, RecoveryCodeDeleteRequest) error
}
UseRecoveryCode must be atomic so concurrent retries cannot consume one code twice. Treat returned codes and request codes as secrets. Selected-edge write restrictions also apply.
WebAuthn
WebAuthnCredential
type WebAuthnCredential struct {
LastUsed time.Time
ID []byte
PublicKey []byte
Transports []string
AAGUID string
Attestation string
Authenticator string
SignCount uint32
BackupState bool
BackupEligible bool
}
| Field | Meaning |
|---|---|
LastUsed | Last successful use time; zero means unknown. |
ID | Binary credential ID. Copy before retention. |
PublicKey | Serialized credential public key. Public cryptographic material, but still account-sensitive. |
Transports | WebAuthn transport identifiers. |
AAGUID | Authenticator attestation GUID text. |
Attestation | Attestation type/metadata text used by the host. |
Authenticator | Authenticator name/type text. |
SignCount | Signature counter. Updates must preserve anti-cloning semantics. |
BackupState | Credential is currently backed up. |
BackupEligible | Credential is eligible for backup/sync. |
The public API does not perform field-level WebAuthn validation. The plugin/backend must preserve IDs and keys exactly and enforce storage constraints.
Request and result structs
| Type and fields | Meaning |
|---|---|
WebAuthnListRequest { Snapshot, Runtime, Username } | Requests all usable credentials for a subject. |
WebAuthnListResult { Status, BackendServer, Credentials } | Returns serving backend and credential values. |
WebAuthnSaveRequest { Snapshot, Runtime, Username, Credential } | Persists one newly registered credential. |
WebAuthnUpdateRequest { Snapshot, Runtime, Username, OldCredential, NewCredential } | Atomically replaces old state, commonly after signature-counter/backup-state changes. |
WebAuthnDeleteRequest { Snapshot, Runtime, Username, CredentialID } | Deletes the credential with the binary ID. |
Every brace member above is an exported struct field. Byte slices and nested slices are request/result-local values; do not mutate inputs to signal host changes.
WebAuthnBackend
type WebAuthnBackend interface {
ListWebAuthnCredentials(context.Context, WebAuthnListRequest) (WebAuthnListResult, error)
SaveWebAuthnCredential(context.Context, WebAuthnSaveRequest) error
UpdateWebAuthnCredential(context.Context, WebAuthnUpdateRequest) error
DeleteWebAuthnCredential(context.Context, WebAuthnDeleteRequest) error
}
Implement on the registered backend object. The list result can influence login/registration flows; writes must be atomic and idempotent where the protocol can retry.
Public MFA state
PublicMFAStateRequest
type PublicMFAStateRequest struct {
Snapshot RequestSnapshot
Runtime RuntimeContext
Username string
IncludeWebAuthn bool
}
IncludeWebAuthn tells the backend whether public WebAuthn credential metadata is required; false permits a cheaper summary-only read. Do not return secret TOTP seeds or recovery code values.
PublicMFAStateResult
type PublicMFAStateResult struct {
Status *StatusMessage
BackendServer *BackendServerRef
WebAuthnCredentials []WebAuthnCredential
RecoveryCodeCount int
HasTOTP bool
HasWebAuthn bool
}
| Field | Meaning |
|---|---|
Status | Status text/key; typed-MFA adapter ignores Temporary. |
BackendServer | Serving backend affinity. |
WebAuthnCredentials | Public/account-safe credential metadata when requested. |
RecoveryCodeCount | Count only, never code values. Use a non-negative value. |
HasTOTP | TOTP is enrolled. |
HasWebAuthn | At least one WebAuthn credential exists. |
PublicMFAStateBackend
type PublicMFAStateBackend interface {
PublicMFAState(context.Context, PublicMFAStateRequest) (PublicMFAStateResult, error)
}
This read interface is used at public identity edges. “Public” means safe for that API purpose, not safe for unrestricted logs or analytics; MFA enrollment remains account-security metadata.