Skip to content

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

  1. Register your site with the standard door. You get a client_id.
  2. Your page's origin must match one of your registered redirect URIs. Calls from any other origin are refused (403 origin_not_allowed); an unknown client_id answers 400 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:

const { available, message } = await accounts.usernameAvailable('luna');

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:

await accounts.setUsername('luna');

Who is signed in

const account = await accounts.me();   // null when signed out

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.

await accounts.logout();

Passkeys

After a password sign-in, offer once to save a passkey. Next time is one press.

if (accounts.passkeySupported()) await accounts.addPasskey();

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:

await accounts.preparePasskeyLogin();
button.onclick = () => accounts.passkeyLogin();

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:

img.src = accounts.avatarOf(account);

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.