Login Actions

Everything else in this documentation is the surface an application integrates with: the OIDC endpoints, the admin API. This page is the other kind. login-actions is the family the portal itself calls — the 21 endpoints behind the login page, the MFA challenge, the password reset and the passkey prompts.

You do not touch them when you connect an application. You touch them when you replace the portal with your own UI and need to drive the same flows.

This is an internal contract, not a stable public API

These endpoints exist to serve FerrisKey’s own portal, and they move with it. If a themed portal gets you where you need to go, use themes and layouts instead — you get the flows for free and they keep working across upgrades. Reach for this family only when theming genuinely cannot express what you need.

Paths below are relative to /realms/{realm_name}.

Password sign-in

MethodPathWhat it does
POST/login-actions/authenticateAuthenticate a user in the realm
POST/login-actions/update-passwordSet a new password, for the update_password required action

authenticate is the entry point. When the realm or the user requires a second factor, it does not return full tokens — it returns a temporary token and a status saying which challenge to run. That temporary token authorizes the challenge and nothing else.

MFA challenge

MethodPathWhat it does
GET/login-actions/setup-otpStart authenticator enrolment: returns the secret to render as a QR code
POST/login-actions/verify-otpVerify the code during enrolment
POST/login-actions/challenge-otpAsk for an authenticator code during sign-in
POST/login-actions/generate-recovery-codesGenerate recovery codes that let the user bypass a challenge
POST/login-actions/burn-recovery-codeSpend one recovery code instead of the challenge

Recovery codes are issued here, during the flow — not from the account page. See Recovery Codes.

Passkeys and WebAuthn

MethodPathWhat it does
POST/login-actions/passkey-request-optionsStart passkey sign-in. With a username, the challenge is scoped to that user’s passkeys; without one, it is discoverable and the browser proposes what it has
POST/login-actions/passkey-authenticateSubmit the browser’s assertion. On success returns a login URL carrying an authorization code
POST/login-actions/webauthn-public-key-create-optionsFull PublicKeyCredentialCreationOptions, challenge in base64url
POST/login-actions/webauthn-public-key-createStore a new WebAuthn public key
POST/login-actions/webauthn-public-key-request-optionsFull PublicKeyCredentialRequestOptions
POST/login-actions/webauthn-public-key-authenticateAuthenticate with a WebAuthnAssertionResponse

The discoverable variant of passkey-request-options is what makes usernameless sign-in possible: no identifier is submitted, so the page cannot be used to probe which accounts exist.

Password reset

MethodPathWhat it does
POST/login-actions/forgot-passwordSend a reset email if the address exists
POST/login-actions/verify-reset-tokenCheck a token is valid and unexpired, without consuming it
POST/login-actions/reset-passwordComplete the reset and return tokens, signing the user straight in

Two deliberate details

forgot-password always answers 204, whether the address exists or not — that is what stops the endpoint being an account-enumeration oracle. Do not “improve” your UI by reporting unknown addresses.

verify-reset-token exists so a reset page can render its form knowing the token is good, without spending it. Check on load, consume on submit.

Email verification

MethodPathWhat it does
POST/login-actions/verify-emailVerify an address with the token from the email
POST/login-actions/resend-verification-emailResend the link. Requires a Bearer token
MethodPathWhat it does
POST/login-actions/send-magic-linkEmail a one-time sign-in link
GET/login-actions/verify-magic-linkConsume the token and return the redirect URL with an authorization code

Device flow

The device code grant needs three endpoints beyond the token endpoint, and they are the pages the user visits rather than the device:

MethodPathWhat it does
GET/deviceThe verification_uri itself. Redirects to the web app, pre-filling the code when passed as ?user_code=
GET/device/previewThe client and scopes a pending device session is asking for, so the page can show what is being approved (RFC 8628 §5.3)
POST/device/verifyApprove or deny, once the user is authenticated

Both preview and verify need the FERRISKEY_IDENTITY cookie. When it is absent, verify answers 401 with a redirect_uri hint pointing back at the verification page — so a custom page can send the user to sign in and return, rather than dead-ending.

preview also refuses codes belonging to another session, which is what stops one user approving a device code that was displayed to someone else.

What you still get for free

Replacing the portal does not mean reimplementing the whole IAM. These keep working underneath, whatever UI calls them: