Skip to content

SDK reference (JavaScript)

Every call in omxus-credential.mjs, the browser SDK for Omxus identities (passkey, phrase, three inputs). How it remembers people, and what to tell them, is on Remembering people. This page is the call-by-call reference.

For ordinary accounts on your own page (email or mobile, username, password, passkey), use omxus-accounts.mjs, which is live: see Your own sign-in page. For an iOS app, see SDK (iOS).

Not reachable from outside yet

omxus-credential.mjs is not served at auth.omxus.com/sdk/ (404, checked 30 Oct 2026) and @omxus/auth is not on npm. The provider's /auth/challenge and /auth/verify send no CORS headers, so a page on another origin cannot finish a sign-in against auth.omxus.com. Today, sign people in with OIDC or Signed in everywhere. This SDK works on a page served by the provider itself, such as your own provider.

Load it

The SDK needs the Omxus engine first. The engine publishes its functions on globalThis; without it every call throws engine not loaded: import engine/engine.bundle.mjs first. The engine is live at https://auth.omxus.com/engine/engine.bundle.mjs (200, CORS open).

import 'https://auth.omxus.com/engine/engine.bundle.mjs';
import { createCredentialStore } from '/sdk/omxus-credential.mjs';

const cred = createCredentialStore({ issuer: location.origin });
const session = await cred.signIn();

createCredentialStore(options)

Returns the store: every call below. All options are optional except issuer.

Option Default What it is
issuer (none) The provider's origin. It goes inside the signed bytes, and the provider rejects any other (Origin mismatch: signed for <origin>).
basePath '' Prefix for /auth/challenge and /auth/verify. Empty means the page's own origin.
rpName 'OMXUS' What the passkey prompt calls the site.
rpId location.hostname Passkey scope.
storage localStorage Where the vault record lives (key omxus.vault.v1).
passkeyTimeoutMs 60000 How long a biometric prompt may go unanswered before the call gives up.
fetchImpl fetch For tests.
wrapKeyStore IndexedDB key Tier 2's key. For tests.
webauthn the browser's {available, create, get}. For tests: Node has no authenticator.

The session

Every call that signs in resolves to the provider's answer plus the address:

{ "address": "0x...", "ok": true, "returnTo": "/", "tier": "passkey-seed" }
  • address is this origin's key, derived per site. It is not the same as cred.address().
  • tier comes with signInWithPasskey(), createPasskeyAndSignIn() and signIn(), except when signIn() unseals an existing vault (step 3 below).
  • The provider sets its session cookie on this answer. returnTo is where the provider was asked to send the person, / if nowhere.

Signing in

signIn()

One press, nothing typed. The call most sites need.

  1. Ask for the passkey the vault names, or any passkey this origin has.
  2. None, or the prompt cancelled or unanswered: make one. This call is the press.
  3. The passkey has no PRF but a sealed vault exists: unseal it and sign in.
  4. No PRF and no vault: generate a phrase, sign in with it, seal it. The person never sees it, and it lives on this device alone (tier: 'device' or 'passkey').

signInWithPasskey()

Tier 0 only. Never makes a passkey.

Resolves to When
a session, tier: 'passkey-seed' A passkey answered with PRF. The vault is written if it was missing.
'none' No passkey here, the prompt was cancelled, or it went unanswered. Offer "Create a passkey".
null No platform authenticator, or the passkey has no PRF. Fall back to a phrase or the three boxes.

createPasskeyAndSignIn()

Make a passkey and sign in with it. Call it only from a "Create a passkey" press, after signInWithPasskey() answered 'none'. Resolves to a session with tier: 'passkey-seed', or null (no authenticator, no PRF, or unanswered).

unlockAndSignIn()

The returning-device button. Unseals the vault (biometric for passkey, none for device), then signs in. For passkey-seed it asks the passkey directly. Throws nothing is enrolled on this device with no vault.

signInWithPhrase(phrase)

Recovery and a new device. Signs in, then seals the phrase here, so it is the last time it is typed on this device. Throws on a bad checksum (see validatePhrase).

signInWithIdentity({ name, dob, words })

The three-box screen. Checks the words first and throws the engine's message if they are too weak (for example Too easy to guess. About 14 more characters would do it...). Signs in, then seals what was typed. The session also carries phrase (the composed secret) and masterAddress. Same three inputs, same identity, on any machine. Why the words carry the whole account: Security model.

addRecovery({ name, dob, words })

Attach the three boxes to a passkey identity so it survives losing every device. The identity changes: it is now the one the three boxes make. The session carries previousAddress, the old cred.address(). Offer it straight after the first sign-in, before anything hangs off the old identity.

The vault

isEnrolled(), address(), tier()

Read the vault. Synchronous, no prompt.

  • address() is the identity's master address, the same on every site (masterAddress after the three boxes). null with no vault.
  • tier() is 'passkey-seed', 'passkey', 'device' or null.

enroll(secret, { address })

Seal a secret at the best tier this device has: passkey if a passkey with PRF can be made in time, else device. Resolves to { tier, address }. Without address the secret must be a valid BIP-39 phrase, or it throws the checksum message. Pass address for a three-box secret.

unlock()

The sealed secret, without signing in. Throws for passkey-seed (this identity is the passkey itself — there is nothing to unseal) and with no vault.

revealPhrase()

unlock() under a name a settings page can use: the phrase, for someone who now wants to write it down, behind the same biometric. Throws for passkey-seed: there is no phrase.

forget()

Remove the vault from this device. The identity still exists; this device stops knowing it.

Phrases

createPhrase()

A fresh 12-word BIP-39 phrase. Returns a promise.

Await it

signInWithPhrase(cred.createPhrase()) fails with mnemonic.trim is not a function. Write await cred.createPhrase().

validatePhrase(phrase)

Resolves to { valid, error } (plus entropy when valid). Checks the checksum, not just the word count: one mistyped word derives a different, empty account. Example error: A recovery phrase is 12 or 24 words. This one has 3.

Lower-level exports

For a page that builds its own flow. Each takes { issuer, basePath, fetchImpl } where it signs in.

Export Does
signInWithPhrase(phrase, opts) Sign in, no vault. Throws signInWithPhrase needs the issuer origin without issuer.
signInWithSeed(bytes, opts) Sign in from 32 seed bytes (a passkey's PRF output).
signInWithIdentity(boxes, opts) Sign in from the three boxes, no vault.
createPasskeyIdentity({ rpName, rpId }) Make a passkey; { credentialId, seed }, or null without PRF.
usePasskeyIdentity(credentialId) The same seed from a named passkey.
usePasskeyIdentityAny() Any passkey this origin has; { seed, credentialId }, or null without PRF.
sealWith(secret, key, opts), unsealWith(vault, key) AES-GCM seal and unseal of the vault record.
deviceWrapKey() Tier 2's non-extractable key, made once in IndexedDB.
passkeySupported() Whether a platform authenticator is available.
browserWebAuthn The default webauthn option.

Checked against sdk/omxus-credential.mjs in the auth repo (30 Oct 2026): every call above run against the provider's router in Node with a stand-in authenticator; the auth repo's sdk/passkey.test.mjs passes 58/58. Untested in a browser outside the auth repo.