Replace your.site with your domain throughout.
1. Publish /.well-known/id
Serve this as JSON at https://your.site/.well-known/id. It lists the exact addresses sign-ins may return to:
{
"version": 1,
"relying_parties": [{
"redirect_uris": ["https://your.site/"],
"client_name": "Your Site"
}]
}
Your client_id is simply your domain, your.site. Only you can publish at your domain, so nobody else can claim it, and every return address must be on it or its subdomains. The names follow OAuth's client metadata (RFC 7591). Can't serve a file there? The same config can go in a DNS TXT record instead: see the config reference.
2. Add the button
Put the button where you want it, and load the SDK from wellknown.id:
<wellknown-id-button></wellknown-id-button> <script src="https://wellknown.id/sdk/web.js" defer></script>
Then, in your page's own script, send the ID token to your server when sign-in succeeds:
window.addEventListener('wellknown-id-login-success', (e) => {
fetch('/session', { method: 'POST', body: e.detail.id_token });
});
window.addEventListener('wellknown-id-login-failure', (e) => {
console.warn('Sign-in failed:', e.detail.error);
});
The page must be one of your redirect_uris (here, your home page). The SDK picks the browser's own sign-in dialog (FedCM) where there is one, or a pop-up, falling back to a full-page visit if pop-ups are blocked. The web SDK has its options.
3. Verify the ID token on your server
It's a standard OpenID Connect ID token, so a stock library checks it. Here it is in Node with jose and Express (npm install express jose):
import express from 'express';
import { createRemoteJWKSet, jwtVerify } from 'jose';
const ISSUER = 'https://wellknown.id';
const jwks = createRemoteJWKSet(new URL(`${ISSUER}/jwks`));
const app = express();
app.post('/session', express.text(), async (req, res) => {
try {
const { payload } = await jwtVerify(req.body, jwks, { issuer: ISSUER, audience: 'your.site' });
// payload.sub is the user's did:key at your site: their account. Start your session here.
res.json({ sub: payload.sub });
} catch {
res.status(401).json({ error: 'not signed in' });
}
});
app.listen(3000);
That checks the signature against wellknown.id's published keys (EdDSA, with ES256 also published), that iss is https://wellknown.id, that aud is your domain, and that the token hasn't expired. The SDK has already checked the token in the browser, but your server must check it again before trusting it.
That's a working sign-in. The sub is the same every time this person signs in to your site as the same persona, and different at every other site.
When someone reaches you with another did:key
A person may have several wellknown.ids, called personas (work and home, say). Each persona gives one did:key per site, the same on every device that holds it, so two personas are two accounts at your site, and nothing tells you they belong together. Which persona signs is chosen on the person's device: the one used at your site before, unless they pick another. Only they can join two, at your site, in two ways:
- A succession statement. When they sign in with the new one, they can tell you it succeeds the earlier one. The ID token then carries
wellknown_succession: a compact JWS (ES256,typ: wellknown-succession+jwt) signed by the earlierdid:key, saying{ iss: earlier, sub: new, aud: your domain, iat }. wellknown.id has checked it; check it again (itskidis adid:key, which carries its public key), then move the earlier account to the newsub, or ask them. - Linking, keeping both. Call
WellknownId.linkKey()from a click while they're signed in. It runs a second sign-in, in which they pick their other wellknown.id, and resolves with its result (also sent aswellknown-id-link-success). Link thatsubto the account they're signed in to.
wellknown.id keeps no record of either: which keys belong together is between them and you.
Next
- Redeem codes on your own backend, with your own key: the web SDK.
- Take the person's own token and verify it yourself, so wellknown.id is sent nothing naming their key: the web SDK.
- Say who may sign in: policies in karu.
- Your own OpenID Connect stack instead of the SDK: endpoints and tokens.