Every method below ends the same way: a single-use authorization code, exchanged at POST /api/v1/oauth/token/ for an access token
(15-minute lifetime), an ID token, and a rotating refresh token (30-day idle/30-day
absolute lifetime for a confidential client, 7-day idle/14-day absolute for a public
one — see Sessions).
PKCE (code_challenge/code_verifier,
S256) is mandatory on every method — there is no non-PKCE path.
POST /api/v1/oauth/authorize/ with email/password.
The password itself is verified by authentik, not by this project — this project never
stores or logs it. Returns a code directly (201); no redirect hop.
POST /api/v1/oauth/magic-link/request/ with
just an email. Always responds identically
whether or not the account exists — there is no way to distinguish "email sent" from
"no such account" from the response, deliberately. The emailed link's token is single-use
and exchanged when the browser opens it.
POST /api/v1/oauth/email-otp/request/ sends a
6-digit code (5-minute expiry, 5 wrong attempts before the request is locked and a new
one is required), then POST /api/v1/oauth/email-otp/verify/ exchanges the code for the same authorization code every other method produces.
GET /api/v1/oauth/google/start/ redirects to
Google's own consent screen; Google redirects back to /api/v1/oauth/google/callback/, which verifies
the returned ID token's signature against Google's real JWKS before trusting anything in
it. For signing into an existing Organization, whether a brand-new email may
self-provision an account this way is governed by that Organization's own allow_self_registration setting
(invite_only by default) — it is not on by
default.
POST /api/v1/oauth/passkey/authentication/options/ with no email — resolution is credential_id-first, so the browser's own passkey picker
is what resolves the account, not a typed-in address. The resulting navigator.credentials.get() assertion is verified at POST /api/v1/oauth/passkey/authentication/verify/,
which also runs sign-count clone/replay detection on every login. Adding a passkey to an
existing account (never a sign-in action itself) is a separate, authenticated pair — POST /api/v1/passkeys/registration/options/ and .../registration/verify/ — under Profile.
An account with TOTP MFA enabled is asked for a second factor on every sign-in, replacing the step-up gate below entirely. An account without it is asked to re-verify (a magic link, email code, or passkey) only from a genuinely new device or a new country — never on every visit. Full detail on Security.
Apple sign-in is not implemented. Sign-in for any Application other than this dashboard and the OneHux Accounts Portal exists mechanically (any registered Application can use the same endpoints) but has no dedicated onboarding UX built around it yet.