Skip to Content
🔐 Faable AuthHosted Login UI

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.

The sign-in screen (desktop)
The sign-in screen (mobile)

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.

The sign-in screen asking for the emailed code (desktop)
The sign-in screen asking for the emailed code (mobile)

Email first

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

The email-first screen (desktop)
The email-first screen (mobile)

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.

The two-step verification screen (desktop)
The two-step verification screen (mobile)

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.

The screen to set up an authenticator app (desktop)
The screen to set up an authenticator app (mobile)

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.

The offer to create a passkey (desktop)
The offer to create a passkey (mobile)

Forgot password

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

The forgot-password screen (desktop)
The forgot-password screen (mobile)

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.

The reset-password screen (desktop)
The reset-password screen (mobile)

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.

The security methods screen (desktop)
The security methods screen (mobile)

Device activation

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

The device activation screen (desktop)
The device activation screen (mobile)

Device confirmation

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

The device confirmation screen (desktop)
The device confirmation screen (mobile)

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:

ParameterWhat 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

WhatWhere
Logo and iconBranding in the dashboard — used as the header of every screen
Which methods, in what order, passkeys on or offLogin Experience, per account and per client
Fewer methods for one particular loginlogin_methods on /authorize
Light or darkLogin Experience, or theme on /authorize for one login
The order the screens run inLogin Flows
Your own domain in the address barCustom Domain
LanguageServed 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>

Last updated on