This document defines the public compatibility contract of Authara.
Its purpose is to make upgrades safe for:
- applications integrating Authara
- SDKs built on top of Authara
- operators deploying Authara in production
If behavior described here changes incompatibly, that change is considered breaking and must follow the versioning policy below.
Authara exposes multiple public contracts:
- OpenAPI contract (public/internal JSON routes, schemas, cookies, and errors)
- configuration contract (environment variables)
- webhook contract (event types, payloads, delivery behavior)
- Redis access-token revocation contract (cross-component key templates)
This document defines the rules and guarantees that apply to all public contracts.
Machine-readable contract definitions live in the contract/ directory.
Authara follows semantic versioning for its public contract.
Patch releases must not break existing integrations.
Allowed in a patch release:
- bug fixes
- security fixes
- performance improvements
- internal refactors
- stricter validation only if it fixes clearly invalid behavior and does not break documented valid usage
- additive non-breaking response fields where consumers can safely ignore them
Not allowed in a patch release:
- removing or renaming public routes
- changing supported HTTP methods for public routes
- renaming cookies
- changing required request fields
- changing stable JSON field names
- changing stable error codes
- changing redirect semantics relied upon by apps or SDKs
- changing webhook payload shape or event names
Minor releases may introduce additive features.
Allowed in a minor release:
- new endpoints
- new optional request fields
- new optional response fields
- new cookies only if existing cookies remain unchanged
- new OAuth providers
- new webhook event types
- new non-breaking SDK integration capabilities
Not allowed in a minor release:
- removing or renaming stable public routes
- removing stable fields or cookies
- changing stable semantics incompatibly
- removing webhook event types
Major releases may include breaking changes.
All breaking changes must be documented clearly in release notes and migration guides.
Authara's first-party Google buttons use Google's popup authorization-code
flow. Deployments with google in AUTHARA_OAUTH_PROVIDERS must configure
AUTHARA_OAUTH_GOOGLE_CLIENT_SECRET in addition to the existing client ID and
register every button-hosting website as an Authorized JavaScript origin.
Legacy API requests containing a Google ID-token credential and nonce remain
accepted for compatibility.
Microsoft login uses the browser authorization-code flow with PKCE and sends a
v2 ID token plus the Core-issued nonce to the Microsoft endpoints. Core keys a
Microsoft identity by tid:oid, validates the tenant-specific issuer and
signature, and accepts email-based account recovery only for Microsoft-verified
email claims. Deployments enable the provider with microsoft in
AUTHARA_OAUTH_PROVIDERS and configure both
AUTHARA_OAUTH_MICROSOFT_CLIENT_ID and AUTHARA_OAUTH_MICROSOFT_TENANT_ID.
Authara uses the following stability levels:
Behavior is part of the public contract and may not break except in a major release.
Behavior still works, but is scheduled for removal in a future major release. Deprecated behavior must be documented before removal.
Not part of the public contract. May change at any time without notice.
Unless explicitly stated otherwise, documented public behavior is considered Stable.
GET /auth/loginPOST /auth/loginGET /auth/signupPOST /auth/signupGET /auth/verify-emailPOST /auth/verify-emailPOST /auth/verify-email/complete
POST /auth/sessions/logout
GET /auth/.well-known/jwks.json
The JWKS contains only public Ed25519 verification keys. Access tokens use
alg=EdDSA and carry a kid matching one of those keys.
Any documented OAuth callback endpoint used by browser-based integrations is part of the public contract once released and documented.
Public JSON authentication operations that declare session_transport accept
cookie (the default) or token. Token transport does not use browser CSRF
credentials and never emits access or refresh cookies; successful session
operations return the token pair in JSON. Cookie-only session refresh and
logout endpoints always retain CSRF protection.
Protected public JSON operations accept either the stable authara_access
cookie or Authorization: Bearer <access-token>. Cookie authentication
requires CSRF proof for mutations. A successfully validated bearer credential
does not, because it is not ambient browser authority. If an Authorization
header is present but malformed or invalid, Core does not fall back to a
cookie.
Native Google, Apple, and Microsoft login options may return a short-lived
challenge_id. The challenge is provider-bound, nonce-bound, shared through
the database for multi-replica deployments, and consumed at most once. Browser
OAuth cookie flows remain the default and continue to work unchanged.
POST /auth/api/v1/tokens/revoke accepts a JSON refresh token, revokes its
session family, and is idempotent for unknown or already-revoked tokens.
For stable public endpoints:
- paths must remain unchanged
- HTTP methods must remain unchanged
- behavior must remain compatible
Changing methods or paths is a breaking change.
Consumers may rely on the meaning of important status codes.
Precise HTML content is not stable unless explicitly documented.
Redirect behavior is part of the public contract.
Used for login, signup, logout, and protected flows.
Use of HX-Redirect is stable once documented.
Stable cookies:
authara_accessauthara_refreshauthara_csrf
- names must not change
- roles must remain consistent
- breaking security semantics is not allowed
Field names must not change or be removed.
{
"error": {
"code": "unauthorized",
"message": "..."
}
}unauthorizedforbiddeninvalid_requestnot_foundrecent_authentication_requiredinvalid_authentication_challengeinternal_error
Stable behaviors include:
- access + refresh token model
- session lifecycle
- logout invalidation
- CSRF protection for browser flows
Security-relevant guarantees must not be weakened.
Sensitive authenticated mutations may return HTTP 428 with
recent_authentication_required when the current session's last password,
passkey, or federated proof is older than the configured window. Token refresh
and organization switching do not update that proof time.
Recent-authentication enforcement is enabled by default. It can be disabled
dynamically with AUTHARA_RECENT_AUTHENTICATION_ENABLED or its operator
override. When disabled, protected routes still require the otherwise valid
session, authorization, and CSRF checks, but do not issue a step-up challenge.
The 428 response includes a short-lived authentication_challenge bound to
the current user and session. Clients complete that challenge through one of
the reauthentication endpoints and then retry the original mutation. A
challenge is single-use; expired, consumed, or session-mismatched challenges
return HTTP 409 with invalid_authentication_challenge.
Application backends can apply the same policy before a sensitive server-side
operation by calling POST /auth/api/v1/reauthenticate/check with the user's
session and CSRF proof. It returns 204 when the policy is satisfied or the
same 428 challenge envelope when step-up is required.
Core exposes two startup-selected revocation guarantees. immediate requires
Redis, checks revocation state on every protected request, and fails closed
when that state cannot be read. Access-token lifetime is capped at 24 hours so
revocation markers can cover tokens issued before a runtime policy reduction.
expiry requires the noop provider and further caps the access-token lifetime
at 10 minutes; logout and authorization changes stop
refresh immediately, but an already-issued access token can remain usable
until its expiry. Production/noop deployments must select expiry explicitly.
Every replica must use the same mode.
Core and server-side SDK middleware in immediate mode share the Redis key
templates in contract/access-token-revocations.json. An incompatible change
is breaking unless a compatible rollout supports both formats.
Users persist the time at which their current email address was verified.
Changing the address clears that proof unless the replacement is completed by
an email challenge or a verified federated identity for the same address.
Public user representations and access tokens expose the boolean
email_verified; user responses also expose email_verified_at when present.
Challenge-disabled signup and admin-created accounts start unverified.
Challenge-backed signup starts verified, and a federated provider marks the
current address verified only when the provider asserts that exact address as
verified.
AUTHARA_EMAIL_VERIFICATION_REQUIRED defaults to false. When enabled, it
applies immediately to existing accounts: an authenticated request carrying an
unverified identity revokes the backing session, records immediate access-token
revocation when Redis revocation is configured, and sends browser users to
/auth/verify-email. The verification flow can confirm the existing address or
atomically replace and confirm it, then issues a new session. Password recovery
for an unverified address is opaque and does not send a usable reset code while
the policy is enabled.
A newly detected passkey sign-counter anomaly creates a durable security event.
AUTHARA_PASSKEY_CLONE_RESPONSE selects alert, restrict, or
restrict_and_revoke; the latter two deny the current authentication and
persistently restrict the passkey, while the last option also revokes all user
sessions using the access-token guarantees in section 8.2.
AUTHARA_PASSKEY_CLONE_NOTIFY_USER controls a generic user notice
that never includes credential IDs, public keys, authenticator IDs, or counter
values. Authenticators that legitimately keep both counters at zero do not
trigger this response.
Password reset rotates an existing password provider. It does not add password authentication to an OAuth-only or passkey-only account. Unknown and passwordless accounts receive the same public accepted response as eligible accounts, without a usable reset code, to prevent account and authentication method enumeration.
A passwordless user must authenticate with an existing provider or passkey before adding a password. Reset completion revalidates the user, current email, and existing password provider before atomically changing the password, revoking sessions, invalidating pending resets, queueing the security notification, and consuming the challenge.
Authenticated password additions, changes, replacements, and removals invalidate outstanding password-reset requests so an older code cannot gain authority over a newly created or changed credential.
An authenticated password change preserves the initiating session and
atomically revokes every other session family, deletes its refresh tokens, and
invalidates outstanding password-reset requests. When a shared revocation
mode is immediate, already-issued access tokens for those sessions are also
rejected immediately. In expiry mode, they remain usable only until the
capped access-token expiry.
Authara exposes two platform roles: authara:admin and authara:operator.
Admin-audience sessions require the admin role, and operator-audience sessions
require the operator role. Neither role implies the other.
If webhooks are configured, the following are part of the public contract:
- webhook event types
- webhook payload envelope
- webhook header names
- signature format
- delivery semantics
Stable webhook guarantees:
- event type names must not change
- payload fields must not be removed or renamed
- headers must remain consistent
- signature format must remain compatible
Additive changes (e.g. new fields or events) are allowed in minor releases.
The machine-readable webhook contract is defined in:
contract/webhooks.yaml
Authara exposes configuration via environment variables.
Stable configuration variables are part of the public contract and follow the same versioning rules.
The machine-readable configuration contract is defined in:
contract/config.yaml
Not part of the contract:
- internal code structure
- database schema
- HTML/CSS details
- internal APIs
- logging format
- internal error messages
Authara maintains contract tests for:
- routes and methods
- cookies
- JSON structure and error codes
- redirect behavior
- configuration surface
- webhook event types and payload shape
- webhook signature format
- Redis access-token revocation keys
Before releasing:
Could an existing integration break without changes?
If yes → breaking change → must not ship as patch.
If consumers can reasonably depend on a behavior, it is part of the contract.
When in doubt, preserve compatibility.