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

  1. Your server asks. createRequest() gives a public half for the page and a secret you keep.
  2. The page shows it. <kv-verify> is a button on the same phone, or a code to scan on a computer.
  3. They approve. In KinshipVault they see your site and exactly what you ask, then use Face ID or fingerprint.
  4. 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

askYou get
over18age_over_18
over21age_over_21
namegiven_name, family_name, birth_date
fullthe 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.

minimumLevelWhat was checked
document_verifiedThe document's chip was authenticated. The person was not checked.
person_verifiedThe document and a selfie, by the identity vendor.
person_verified_chipBoth, 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

Try it

kinshipvault.app/verify runs the same checks in your browser, with the same code.