KinshipVault
Verify KinshipVault IDs on your site
Check that a visitor is over 18 or over 21, or get their verified name, with @kinshipvault/verify. Your server makes the request and checks the answer, so you trust the ID's own signatures, not a call to us. No personal data passes through KinshipVault. Open source (MIT), no dependencies, Node 20 or later.
How it works
- Your server asks.
createRequest()gives a public half for the page and a secret you keep. - The page shows it.
<kv-verify>is a button on the same phone, or a code to scan on a computer. - They approve. In KinshipVault they see your site and exactly what you ask, then use Face ID or fingerprint.
- Your server checks.
verifyResponse()checks the signatures, your page's address, the dates and revocation.
Get it
The package, ready to copy into your project:
On your server
const { createRequest, verifyResponse } = require('@kinshipvault/verify');
app.post('/age-check/start', async (req, res) => {
const r = await createRequest({ ask: 'over18', origin: 'https://shop.example',
siteKey: SITE_KEY, // your site's key, below
minimumLevel: 'person_verified' }); // optional
req.session.kvSecret = r.secret; // stays on the server, used once
res.json(r.public);
});
app.post('/age-check/finish', async (req, res) => {
const secret = req.session.kvSecret;
delete req.session.kvSecret;
const result = await verifyResponse(secret, req.body);
// { ok: true, ask: 'over18', claims: { age_over_18: true }, level: 'person_verified', revocation: 'valid', ... }
res.json({ ok: result.ok && result.claims.age_over_18 === true });
});
On your page
<script src="/vendor/kinshipvault/qr.js"></script>
<script src="/vendor/kinshipvault/widget.js"></script>
<kv-verify></kv-verify>
<script>
const el = document.querySelector('kv-verify');
fetch('/age-check/start', { method: 'POST' }).then((r) => r.json()).then((pub) => { el.request = pub; });
el.addEventListener('kv-answer', (e) => fetch('/age-check/finish', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(e.detail) }));
</script>
Serve both scripts from your own site. Allow the relay in your Content-Security-Policy for the code: connect-src https://ajdcjlgcwjqbqtblgrfk.supabase.co. The widget speaks English, French, German and Japanese, and takes your colours through CSS variables.
What you can ask
| ask | You get |
|---|---|
over18 | age_over_18 |
over21 | age_over_21 |
name | given_name, family_name, birth_date |
full | the name, plus document_number, expiry_date, issuing_country |
How well the ID was checked
Set minimumLevel to refuse an ID checked less well than you need. The answer then carries its level, and a lower one is refused as level_too_low.
| minimumLevel | What was checked |
|---|---|
document_verified | The document's chip was authenticated. The person was not checked. |
person_verified | The document and a selfie, by the identity vendor. |
person_verified_chip | Both, and the chip agrees with the vendor. |
Your site's key
When the person scans your code with another phone, their app shows your site's address. Sign your requests and the app confirms it; without a key it shows your address as "Not confirmed". Make a key once:
const { generateSiteKey } = require('@kinshipvault/verify');
const { privateJwk, publicJwks } = await generateSiteKey();
// privateJwk: secret, on your server; pass it to createRequest as siteKey
// publicJwks: publish it at https://shop.example/.well-known/kinshipvault-site.json
The app reads that file from your site itself, with no redirect, so serve it directly with status 200 (at most 16 KB). To change keys, publish the new one beside the old one, switch, then remove the old one.
Security
- The secret opens the answer. Keep it on your server, tied to the visitor's session, and use it once.
- On the same phone, the answer is bound to your page's address. A copy of your page elsewhere gets an answer that does not open on your server.
- Across two devices, it is bound to your site too. The code names your address and the person's phone signs over it, so an answer given to a copy of your code under another site's name does not verify on your server. A copy that keeps your name shows your name to the person, confirmed when you sign your requests. The code needs https.
- What is trusted. IDs issued by KinshipVault, under the root at Accepting KinshipVault credentials. IDs in Apple Wallet or Google Wallet are outside this package.
Try it
kinshipvault.app/verify runs the same checks in your browser, with the same code.