Hosted Login UI
You do not have to build the authentication screens for your project. Sign-in, sign-up, password reset, two-step verification, passkey enrolment, device authorisation — Faable Auth serves all of them on your auth domain, with your logo, and you never write a login form.
That is the Hosted Login UI. Your application sends the user to /authorize and gets them back authenticated; everything in between is these screens. If you would rather build your own, you can — the same endpoints are there — but everything below is what you would be re-implementing.
Why they are hosted, and not a form in your app
This is not about saving you an afternoon of HTML. Four things only work from the auth domain:
A passkey belongs to the origin that created it. WebAuthn binds the credential to the domain, so a passkey created on app.example.com cannot be used to sign in at auth.example.com — or at your second application. Registering them from one place is what makes them work everywhere.
The session is a cookie on the auth domain. That cookie (faable_sess) is what lets a second application skip the login screen entirely: the next /authorize finds an existing session and returns without asking for anything. That is single sign-on, and it cannot happen if each app holds its own form.
Credentials never reach your code. A password is posted from the login screen to the auth server. Your application never sees it, never logs it by accident, and a compromise of your frontend does not become a compromise of your users’ passwords.
One place to improve. Two-step verification, passkeys, rate limits, the suspended-account message, the wording of an expired code — they land on every one of your applications at once, without a release on your side.
The screens
All of them live under /flow on your auth domain (https://<your-account>.auth.faable.link, or your custom domain). A user reaching one of these without a session is sent through the login and brought back where they were.
Every screen adapts to the device: a card on a desktop browser, full width on a phone. The captures below come from Faable’s own tenant, so they carry our logo; yours carry the logo you set in Branding.
Sign in
/flow/login — the methods this client offers, in the order you chose: here a one-time code by email, email and password, then GitHub and Google.


Sign in with an email code
/flow/login — after Send me a code, the same screen asks for the 6-digit code emailed to the user, one box per digit. It signs in as soon as the sixth digit is in — typed, pasted, or filled in by the phone from the email — with no button to press. The code lasts 5 minutes; the user can ask for another once the countdown ends, or go back and use another address. See Passwordless for the email itself.


Email first
/flow/identify — asks only for the address before showing a method, then continues with the one that fits that user.


Two-step verification
/flow/mfa/challenge — asks for the second factor: an authenticator app, a security key or a passkey. The user’s default comes first, and a recovery code is the way out when they have none of them at hand.


Add a second factor
/flow/mfa/enroll — enrols an authenticator app: a QR code, a fallback for when the camera can’t scan it, and a first code to confirm it works — followed by the recovery codes.


Passkey offer
/flow/passkey/offer — shown right after a login, on browsers that support passkeys, inviting the user to create one. Not now carries on to your application.


Forgot password
/flow/forgot-password — requests the reset instructions. If the user has a phone on file, it also asks where to send them.


Reset password
/flow/reset-password — sets the new password. The requirements under the field come from the tenant’s password policy, and are checked before the form is sent.


Security methods
/flow/account/security — where a signed-in user manages their own authenticator apps and passkeys. It is the one screen your users reach on their own — see below.


Device activation
/flow/device-code/activate — where the user types the code a CLI or a TV app is showing.


Device confirmation
/flow/device-code/confirm — names the device and repeats the code, so the user can check it matches before authorising it.


What your application does
Nothing but the redirect. With @faable/auth-js:
import { createClient } from '@faable/auth-js'
const auth = createClient({
domain: 'https://<your-account>.auth.faable.link',
clientId: '<your-client-id>'
})
// Sends the browser to the hosted login and returns with a session.
await auth.authorize()Or, without the SDK, any OAuth 2.0 / OIDC library pointed at /authorize — the screens are the same either way. See OAuth 2.0 Flows.
Showing only some methods
A client can offer several ways to sign in — a password, an emailed code, GitHub, Google — and one part of your application may want only some of them. Say which with login_methods on /authorize: connection ids or names, separated by commas, plus passkey.
// @faable/auth-js 2.10 or later. The same client, but this login shows only
// the emailed code ('email' is that connection's name; its id works too).
auth.authorize({ response_type: 'code', loginMethods: ['email'] })https://<your-auth-domain>/authorize?client_id=<your-client-id>&login_methods=email&…Two parameters do different things, and they cannot be used together:
| Parameter | What the user sees |
|---|---|
connection=<id> | No screen: they go straight to that connection (GitHub, Google, your IdP) |
login_methods=<ids> | The screen, with only those methods, in the order configured for the client |
Each method in login_methods must be one the client already offers — anything else is refused with invalid_connection — so the parameter can narrow a client’s screen, never widen it. A list with a single social connection behaves like connection and skips the screen.
login_methods changes what the screen shows. It does not stop someone from using another method the client offers by calling the API directly: to take a method away for good, remove it from the client in Login Experience.
Forcing light or dark
The screens follow the visitor’s light or dark preference unless you set a theme for the account or the client. To force one for a particular login — a page of your app that is always dark, say — pass theme to /authorize:
// The same client; this login always shows the dark screens.
auth.authorize({ response_type: 'code', queryParams: { theme: 'dark' } })https://<your-auth-domain>/authorize?client_id=<your-client-id>&theme=dark&…theme takes light or dark; anything else is refused with 400. It applies to every screen of that login — the email, the password, the second factor — not only the first, and it hides the visitor’s light/dark switch. It is not sent on to a social provider such as Google or GitHub.
Making them yours
| What | Where |
|---|---|
| Logo and icon | Branding in the dashboard — used as the header of every screen |
| Which methods, in what order, passkeys on or off | Login Experience, per account and per client |
| Fewer methods for one particular login | login_methods on /authorize |
| Light or dark | Login Experience, or theme on /authorize for one login |
| The order the screens run in | Login Flows |
| Your own domain in the address bar | Custom Domain |
| Language | Served in English or Spanish, chosen from the browser’s Accept-Language |
Because a passkey is bound to its origin, move to a custom domain before users start enrolling — or set a WebAuthn Relying Party ID you own. See Login Experience.
The one screen your users go to on their own
/flow/account/security is the exception: it is not part of a login, and you can link to it from your own account settings. A user manages their authenticator app, security keys and passkeys there, on the domain those credentials belong to.
Link to it with the client_id of the application they came from, so a user who arrives without a session is sent back to the right login:
https://<your-auth-domain>/flow/account/security?client_id=<your-client-id>Related
- Login Experience — what the sign-in screen offers, and how to change it.
- Two-Step Verification — the second factor these screens ask for.
- Login Flows — the graph the screens walk through.
- Custom Domain — serving them from a domain you own.
Last updated on