Your own sign-in page¶
People sign up and sign in on your page, in your layout. Nobody is sent to auth.omxus.com. It feels like any normal site: email or mobile, a username, a password or a passkey.
omxus-accounts.mjs does the work. It has no DOM and no CSS: you draw the
screens, it makes the calls, holds the session and refreshes it. Every error it
throws has a message you can show to the person as it is.
Use this when a redirect is wrong for your site. Otherwise OIDC is less work.
Before you start¶
- Register your site with the standard door. You get a
client_id. - Your page's origin must match one of your registered redirect URIs. Calls
from any other origin are refused (
403 origin_not_allowed); an unknownclient_idanswers400 unknown_client.
Load it¶
The file is served by the provider:
import { createAccounts } from 'https://auth.omxus.com/sdk/omxus-accounts.mjs';
const accounts = createAccounts({
issuer: 'https://auth.omxus.com',
clientId: 'YOUR_CLIENT_ID',
});
Or copy it into your own build; it has no dependencies.
Sign up¶
const { account, sent } = await accounts.signup({
email: '[email protected]', // or phone: '+61 4...'
username: 'luna',
password: 'a long password', // optional: leave it out for a passkey or code-only account
});
The person is signed in at once, but restricted (account.verified is
false) until they type the 6-digit code sent to their email or mobile. Let
them browse; hold back posting, voting and uploading.
const account = await accounts.verify('123456'); // account.verified === true
await accounts.resend(); // a new code
Check a username while they type:
Sign in¶
Three ways in. Offer all three.
// Email or mobile, and password
const account = await accounts.login({ email, password });
// A code instead (forgot password, or no password at all)
await accounts.codeStart({ email });
const account = await accounts.codeFinish({ email, code });
// A passkey (see below)
const account = await accounts.passkeyLogin();
An account with no username for your site has account.username === null.
Ask for one, once:
Who is signed in¶
Call it when the page loads. Tokens live in localStorage (memory if that is
blocked). The access token lasts 15 minutes; the SDK refreshes it before it
runs out, and once more after a 401, so you never handle a token.
Passkeys¶
After a password sign-in, offer once to save a passkey. Next time is one press.
Safari only shows the passkey sheet if the request starts inside the tap. Fetch the options early, when the page loads, so the tap has nothing to wait for:
For passkeys in the email field's autofill, give the field
autocomplete="username webauthn" and call
accounts.passkeyLogin({ conditional: true, signal }). More in
Passkeys.
Pictures¶
Nobody is a blank circle. Draw whatever this returns:
It is the picture they picked, or one drawn from their id. Never use
account.avatar.url directly. More in Profiles and claims.
Errors¶
Every method throws an Error. error.message is written for the person;
error.code is the server's code for you.
try {
await accounts.login({ email, password });
} catch (e) {
showUnderForm(e.message);
if (e.code === 'reauth_required') { /* ask for the password again */ }
}
Codes you will see often: username_taken, weak_password (under 8
characters), breached_password, bad_code, reauth_required, too_many_requests,
network.
What the SDK does for you¶
- Passwords never leave the browser. It stretches the password with PBKDF2-SHA256, 600,000 rounds, salted per contact, and sends only that.
- Breached passwords. Before a new password is sent it asks Have I Been Pwned with the first 5 characters of the password's SHA-1 hash. If that service is down, it carries on.
- Imported accounts. A site that brought its old users across: on their first sign-in the SDK sends the password once, over TLS, so the old hash can be checked and replaced. See Migrating an existing login.
- One refresh at a time, across tabs (Web Locks), so two tabs never burn each other's refresh token.
Account settings¶
The same object changes the password, the contact, the profile and the
sessions: setPassword, changeContactStart / changeContactFinish,
setProfile, uploadAvatar, sessions, signOutEverywhere, exportData,
deleteAccount. The routes behind them, and which need a recent sign-in, are
in Sessions, activity and the account and the
HTTP reference.
Tested
Every method on this page was checked against the live
auth.omxus.com/sdk/omxus-accounts.mjs (30 Oct 2026): signup, verify,
me, avatarOf and logout run against a stand-in server and send the routes
listed in the HTTP reference. Not yet run end to end against a registered
live client.