The drop-in button¶
For a site that does not speak OIDC, or one that wants sign-in to happen where the person already is rather than on a login page.
Not ready for sign-in (checked 30 Oct 2026)
Loaded on a page today, the button shows "omxus is unavailable": it
imports https://auth.omxus.com/sdk/omxus-credential.mjs, which answers
404. And even once that file is served, what the button posts to your
server carries no proof your server can check (see
What your endpoint receives). Until both
are fixed, sign people in with OIDC or
Signed in everywhere.
One tag¶
<script src="https://auth.omxus.com/button.js"
data-omxus
data-verify="/api/auth/omxus"
data-label="Login"
data-reload></script>
| attribute | meaning |
|---|---|
data-verify |
your endpoint, below. Default /api/auth/omxus |
data-label |
the word on the button. Default Login (Add to Omxus for the wallet look) |
data-variant |
login (round button, default) or wallet (pill with the Omxus mark) |
data-home |
where the small "omxus" link under the button opens, in its own window. Default https://auth.omxus.com |
data-reload |
reload on success; leave it off and listen instead |
data-omxus-mount |
put on any element to render the button there; otherwise it renders where the tag sits |
Style it with variables, or leave it:
:root{
--omxus-size:132px; /* round button only */
--omxus-face:#0d8b88;
--omxus-ring:#d4af37;
--omxus-word:#fff;
}
Listen instead of reloading:
document.addEventListener('omxus:signin', (e) => { /* e.detail.session */ });
document.addEventListener('omxus:error', (e) => { /* e.detail.error */ });
The same events, without the omxus: prefix (signin, error), also bubble
from the script tag itself.
What happens on a press¶
From worker/button.js in the auth repo, which is what auth.omxus.com serves:
- On load it imports
/engine/engine.bundle.mjs(served) and/sdk/omxus-credential.mjs(404 today) from auth.omxus.com, and makes a credential store for issuerhttps://auth.omxus.com. - On a press it calls the store's
signIn(): a passkey if the device has one, otherwise a new passkey, otherwise a key sealed on this device. See Memory, not storage. signIn()asks for a challenge and posts the signature to/auth/challengeand/auth/verifyon your page's own origin (the store is made with no base path). Your site would need to forward those to auth.omxus.com; the provider does not send CORS headers for/auth/*.- It posts the result to
data-verifyand firesomxus:signin.
What your endpoint receives¶
{
"issuer": "https://auth.omxus.com",
"session": { "address": "0x…", "ok": true, "returnTo": "/", "tier": "passkey-seed" }
}
session is the answer from /auth/verify plus the address. The provider's
session token goes into a cookie on the provider's origin; it is not in
the body. So the body is only a claim: anyone can post any address to your
endpoint. Do not create a session from it.
/oauth/introspect does not help here. It reads a form-encoded body, needs
your client id and secret, and only answers for OIDC access tokens issued to
your client; anything else comes back {"active": false}. To check an
access token you do hold:
curl -s https://auth.omxus.com/oauth/introspect \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "token=$ACCESS_TOKEN"
# {"active":true,"scope":"openid profile","client_id":"…","token_type":"Bearer","sub":"…","exp":…,"iat":…}
Untested against a live token here; the request and answer shapes are from
server/oidc-introspect.js and server/router.js. The answer has sub, not
an address.
Where to put it¶
Not on /login
A login page is a toll gate. Put the button at the moment somebody reaches for the thing they came for — the feature, the upload, the vote. They press once and carry on, on the same page.
One site lost most of 2,000 daily visitors to a login flow in front of a feature people actively wanted. The feature was never the problem.