Hosted login
The sign-in screens Authio serves for each client: the steps they ask for, how they look, the languages they speak and how the sign-in returns to your app.
Every client gets a set of screens that Authio hosts and renders on the server: sign-in, the second factor, forgotten password, terms, consent, sign-out and the account pages. Your app never collects a password. It sends the browser to the realm’s authorization endpoint, and Authio walks the person through whatever the client is configured to ask for.
This page covers how a sign-in runs, the steps you can compose per client, what the person sees, branding and languages, and how to run the sign-in in a popup instead of a full redirect.
How a sign-in runs
Your app sends an authorization request
A standard authorization code request with PKCE, sent to the realm’s authorization endpoint. Any OpenID Connect library builds it for you:
Authorization requestPlain texthttps://authio.api.hyperio.tech/realms/acme/protocol/openid-connect/auth ?client_id=acme-web &response_type=code &redirect_uri=http://localhost:3000/api/auth/callback/authio &scope=openid profile email &state=STATE &code_challenge=CODE_CHALLENGE &code_challenge_method=S256Authio looks for a session it can reuse
If the client takes part in single sign-on and the browser already holds a valid sign-in to the realm, the person goes straight through, or picks an account from the chooser. Otherwise the browser lands on the client’s sign-in screen,
/realms/acme/clients/acme-web/login, carrying the whole request with it.The hosted login runs the client’s steps
Password, passkey, a provider button, then whatever second factor the client still owes. After the credential is proven, Authio applies the realm’s checks in order: suspension, account status and access to this client, the sign-in address, password expiry, and only then the second factor.
Terms and consent, when the client asks for them
With the account settled, the request goes back through the authorization endpoint. The terms screen comes first if the account has not accepted the current version, then the consent screen if the client asks for consent and a scope is still ungranted.
The browser returns to your app
Authio redirects to the registered redirect URI with
code,stateandiss. Your app exchanges the code for tokens as usual.
The request is never lost between steps
Sign-in steps
Each client chooses, in order, which steps its sign-in is made of. Clients of the same realm can differ: one asks for a password only, another for a passkey, a third for a password plus a phone approval.
| Step | What it asks for | Notes |
|---|---|---|
login | Username and password. | The only step that can admit an account with nothing enrolled. The provider buttons appear under it. |
mfa | A 6-digit code sent to the account’s e-mail address. | Asked after a first factor; the same code also reaches the account’s paired phones. See Multi-factor. |
passkey | A passkey on this device: fingerprint, face, screen lock or a security key. | Works as the first step or as a second factor. See Passkeys. |
push | An approval in the Authio app, matching a number shown on the screen. | Always a second factor. See Multi-factor. |
terms_acceptance | Acceptance of the current version of the client’s terms. | Always asked after sign-in, wherever it sits in the list. See Sessions and SSO. |
Requirement and registration
The passkey and push steps carry two settings. Requirement says how strongly the step is asked for; Registration says what happens to an account that does not have the factor yet.
| Setting | Value | Effect |
|---|---|---|
| Requirement | Preferred (default) | Asked only of accounts that can already answer it: a registered passkey or a paired phone. |
| Requirement | Optional | Never blocks a sign-in. An authorization request with acr_values can raise it to Preferred. |
| Requirement | Required | Asked of every account, including one with nothing enrolled. |
| Registration | Offer (default) | An account without the factor is offered to set it up and can decline. |
| Registration | Never | Nothing is offered during sign-in; the factor is set up from the account pages. |
| Registration | Require | The account has to set the factor up before it can continue. |
Combinations that would lock people out are refused when you save the client, with the reason on the field:
- A Required step with registration Never on a client that has a
loginstep: an account without the factor could never get in. - An Optional step with registration Require: the account would enrol a factor that is never asked for.
- An
mfastep marked Optional: the code would never be sent. Remove the step instead. - A step that offers registration on a client with no
loginstep: there is no session to register from, so set registration to Never.
Configure the steps
Open the client’s login layout
In Clients, open the client and choose Edit, then the Login layout step. The steps come first, top to bottom in the order the screen asks for them.
Add, remove and order the steps
Move each step up or down, and set Requirement and Registration for
passkeyandpush. Terms acceptance is a separate switch marked After sign-in; its documents and version are set in the client’s Configuration.Save
The new steps apply from the next sign-in. A client that uses the authorization code flow needs at least one step; the save is refused without it.
What the person sees
Around the steps, the sign-in screen offers:
- Remember me, which keeps the session after the browser closes.
- Forgot password?, which opens the password reset for this client.
- One Continue with … button per identity provider the client offers, under the sign-in button.
- Passkey autofill in the username field, when the client also has a
passkeystep. - Start over, which drops what was entered so far and reopens the first step with the same request.
- The account chooser, when the browser already holds more than one reusable sign-in.
An unknown username and a wrong password get the same answer, so the screen never reveals which accounts exist. Every other refusal is shown only after the password was accepted, and says what to do next:
| The person sees | Why | How it is resolved |
|---|---|---|
| Those details did not match | Wrong username or password. | Try again; repeated failures count toward the realm’s lockout. |
| Too many attempts | The account is locked by the realm’s lockout rules. | The message gives the duration; resetting the password gets in sooner. |
| Address not confirmed yet | The account’s e-mail address was never confirmed. | The screen offers to send the confirmation e-mail again. |
| No access to this application | The client admits only linked accounts and this one is not linked. | An administrator grants access from the client’s users. |
| Confirm this location | First sign-in from a new address. | The screen waits and continues by itself once the address is approved. See address approval. |
| Account suspended | A suspension is in force. | The message says when it ends, and the reason if the administrator chose to show it. |
| Your password has expired | The realm requires periodic renewal. | The person is taken to renew it by e-mail. |
| This account is not active | The subscription behind the application has lapsed. | Sign-in resumes when the subscription is renewed. |
Failures that no retry can fix, such as a request whose client_id names a different client than the screen, replace the form with an error card that carries a reference code support can find in the logs.
Branding
All hosted screens of a client wear its appearance: sign-in, recovery, terms, consent, errors, device verification and the account pages. You set it in the same Login layout step.
| Setting | What it controls | Limits |
|---|---|---|
| Arrangement | Panel beside (brand panel next to the form, the default), Centered (one column, card in the middle) or Cover (brand across the page, card raised over it). | — |
| Title and subtitle | The words above the form. | Up to 200 and 500 characters. |
| Colors | Primary, secondary and background, once for the light theme and once for the dark one. | Hexadecimal #RGB or #RRGGBB. |
| Logo and background image | Images loaded from your own URLs. Without a logo, the platform’s is shown. | Up to 500 characters; the background image must use https. |
| Custom CSS | Your own rules on top of the screens. | Up to 4,000 characters, sanitized (see below). |
Custom CSS cannot contain <, expression(, javascript:, vbscript:, behavior: or -moz-binding, and cannot load url() or @import over plain http. It is checked when you save and sanitized again when the screen is drawn.
A plan feature
features.AuthLayoutCustom). On other plans the console shows the Login layout step locked, with the reason and a way to the plans, and the API refuses appearance changes with AuthLayoutNotIncluded. See Plans and billing.When an authorization request names an organization with organization, that organization’s logo, primary color and background color replace the client’s; the arrangement, the texts and the CSS stay the client’s.
Light and dark
Pass theme=light or theme=dark on the authorization request to choose the palette people start with. The screens also have a toggle; a person’s own choice is kept in a cookie and wins until the next request that names a theme.
Languages
The hosted screens are written in English (en) and Brazilian Portuguese (pt-BR). The language is settled once per sign-in and kept across every step:
ui_localeson the authorization request, a space-separated list in order of preference.- The browser’s
Accept-Languageheader, respecting its weights. - English.
Errors are worded from their code in the visitor’s language. Every hosted page, the account pages included, also accepts ui_locales and theme directly in its address.
Parameters the screens read
Besides the standard OAuth parameters, these change what the hosted login shows:
| Parameter | Values | Effect |
|---|---|---|
ui_locales | en, pt-BR | Language of every screen in the sign-in. |
theme | light, dark | The palette the screens start in. |
display | page (default), popup | popup draws the screens centered, to fit a small window. |
response_mode | query (default), web_message | web_message hands the response to the window that opened the popup. |
prompt | login, select_account, consent, none | Ask for credentials again, show the account chooser, show the consent screen, or never show a screen. |
max_age | Seconds | A sign-in older than this is asked for again. |
acr_values | 1, 2, 3 | Asks for a stronger sign-in, best effort. See step-up. |
organization | An organization alias | Uses that organization’s branding and identity providers. |
Sign in in a popup
To keep your page where it is, open the authorization request in a popup window. Add display=popup so the screens fit the window, and response_mode=web_message so that, at the end, Authio posts the response to the window that opened the popup and closes it, instead of navigating the popup to your redirect URI.
The message is an object whose type is authorization_response and whose response holds code, state and iss (or error). It is posted only to the origin of the redirect URI the request named, so the page that opens the popup has to be on that origin. If no window is waiting — the popup was opened as a tab, or its opener navigated away — the popup falls back to an ordinary redirect.
const ISSUER = 'https://authio.api.hyperio.tech/realms/acme'
const AUTHIO_ORIGIN = new URL(ISSUER).origin
export function signInWithPopup(
state: string,
codeChallenge: string,
): Promise<Record<string, string>> {
const authorize = new URL(ISSUER + '/protocol/openid-connect/auth')
authorize.search = new URLSearchParams({
client_id: 'acme-web',
response_type: 'code',
redirect_uri: 'http://localhost:3000/callback',
scope: 'openid profile email',
state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
display: 'popup',
response_mode: 'web_message',
}).toString()
const popup = window.open(authorize.toString(), 'authio-sign-in', 'popup,width=480,height=720')
return new Promise((resolve, reject) => {
if (!popup) {
reject(new Error('popup_blocked'))
return
}
function onMessage(event: MessageEvent) {
if (event.origin !== AUTHIO_ORIGIN || event.source !== popup) return
if (event.data?.type !== 'authorization_response') return
window.removeEventListener('message', onMessage)
resolve(event.data.response)
}
window.addEventListener('message', onMessage)
})
}Check that state matches the one you sent, then exchange code with your PKCE verifier exactly as after a redirect. Call the function straight from a click handler, or the browser blocks the popup.
authio-provider-nextauth 2.1.0 and later ship the popup flow ready-made in authio-provider-nextauth/popup, with the code exchange still done by Auth.js on your server. See Next.js.Where accounts come from
The hosted login has no self-service sign-up form. An account reaches a realm in one of three ways:
- Created by an administrator in Users or through the Admin API.
- Imported from another provider. See User migration.
- Created on its first sign-in through an identity provider set to create accounts. See Social and enterprise sign-in.