Authentication Playground
Test every Hesskey authentication method — QR, BLE, and trustless light client
Web3 Challenge-Response (CAIP-122)
The simplest form of decentralized authentication. Proves you control a DID without sharing any personal data.
How it works
How to implement
Drop the SDK into any HTML page. The SDK handles session creation, QR rendering, and polling:
<button id="signin">Sign in with Hesskey</button>
<div id="qr"></div>
<script type="module">
import { HessKeyAuth }
from 'https://your-verifier.com/shared/hesskey-sdk.js';
const auth = new HessKeyAuth({
verifierApiUrl: 'https://your-verifier.com',
domain: window.location.hostname,
authType: 'web3',
});
document.getElementById('signin').onclick = async () => {
const session = await auth.createSession();
auth.renderQR(document.getElementById('qr'), { size: 256 });
auth.onVerified((result) => {
console.log('Signed in as', result.result.did);
});
};
</script>
Server-side prerequisites:
- A running Hesskey verifier (
hesskey-web/server.py) - A Hesskey chain node for DID resolution and signature verification
- No database required — sessions are stored in-memory with a 5-minute TTL
SIOP v2 + OID4VP
OpenID-based authentication with Verifiable Credentials. The website requests specific identity data and you choose what to share.
How it works
How to implement
Declare exactly which credential fields you need with a W3C Presentation Definition:
<script type="module">
import { HessKeyAuth }
from 'https://your-verifier.com/shared/hesskey-sdk.js';
const auth = new HessKeyAuth({
verifierApiUrl: 'https://your-verifier.com',
domain: window.location.hostname,
authType: 'siop_vp',
presentationDefinition: {
id: 'login',
inputDescriptors: [{
id: 'identity',
name: 'Identity Credential',
constraints: {
fields: [
{ path: ['$.credentialSubject.givenName'] },
{ path: ['$.credentialSubject.familyName'] },
{ path: ['$.credentialSubject.nationality'] },
],
},
}],
},
});
document.getElementById('signin').onclick = async () => {
await auth.createSession();
auth.renderQR(document.getElementById('qr'), { size: 256 });
auth.onVerified((result) => {
const vp = result.result.vp;
const cred = vp.verifiableCredential[0];
console.log('Hello', cred.credentialSubject.givenName);
});
};
</script>
What the verifier checks automatically:
- VC issuer is a trusted CSCA (passive authentication chain)
- VC signature (eddsa-jcs-2022) is valid against the holder's on-chain DID
- id_token (SIOP JWT) nonce matches the session
- VP is bound to your
client_id— cannot be replayed elsewhere
BBS+ ZKP + ECDH Encrypted Claims
Maximum privacy authentication. Prove facts about yourself without revealing the underlying data, and encrypt shared claims so only the verifier can read them.
ZKP Predicates requested:
Fields to encrypt (ECDH pairwise):
How it works
How to implement
Request predicates and encrypted fields. The verifier decrypts with its X25519 private key server-side:
<script type="module">
import { HessKeyAuth }
from 'https://your-verifier.com/shared/hesskey-sdk.js';
const auth = new HessKeyAuth({
verifierApiUrl: 'https://your-verifier.com',
domain: window.location.hostname,
authType: 'bbs_ecdh',
predicates: [
{
fieldPath: '$.credentialSubject.dateOfBirth',
predicate: 'AGE_OVER',
value: 18,
verifierDid: 'did:hesskey:your-verifier',
},
],
encryptedFields: ['given_name', 'family_name'],
presentationDefinition: {
id: 'kyc',
inputDescriptors: [{
id: 'identity-bbs',
constraints: {
fields: [
{ path: ['$.credentialSubject.dateOfBirth'] },
{ path: ['$.credentialSubject.givenName'] },
{ path: ['$.credentialSubject.familyName'] },
],
},
}],
},
});
document.getElementById('signin').onclick = async () => {
await auth.createSession();
auth.renderQR(document.getElementById('qr'), { size: 256 });
auth.onVerified((result) => {
// ZKP proofs verified on-chain by the verifier
console.log('Predicates:', result.result.predicates);
// Encrypted claims decrypted server-side
console.log('Claims:', result.result.encryptedClaims);
});
};
</script>
Server-side prerequisites:
- Register your verifier DID on-chain with an X25519 key-agreement key
- Keep the X25519 private key on the server — never ship it to the browser
- Cryptographic erasure: call
revoke_claim_accesson-chain to make stored ciphertext for that user permanently unreadable
BLE Direct Login
Sign in without scanning a QR code. Your browser connects directly to your phone over Bluetooth Low Energy and delivers the auth request — no QR scan, no relay server.
How it works
How to implement
For direct Web Bluetooth, call navigator.bluetooth.requestDevice() in the click handler (before any await) to preserve the user gesture, then write the auth URI over GATT:
<script type="module">
import { HessKeyAuth, isExtensionPaired }
from 'https://your-verifier.com/shared/hesskey-sdk.js';
const BLE_SERVICE = 'ae55be01-c0de-e5c1-b1e1-000000000001';
const BLE_AUTH = 'ae55be01-c0de-e5c1-b1e1-000000000010';
document.getElementById('signin').onclick = async () => {
// 1. Request device BEFORE any await (user gesture)
let device = null;
if ('bluetooth' in navigator && !isExtensionPaired()) {
device = await navigator.bluetooth.requestDevice({
filters: [{ services: [BLE_SERVICE] }],
});
}
// 2. Create session (SDK starts polling automatically)
const auth = new HessKeyAuth({
verifierApiUrl: 'https://your-verifier.com',
domain: window.location.hostname,
authType: 'siop_vp',
presentationDefinition: { /* ... */ },
});
const session = await auth.createSession();
// 3. Write auth URI over BLE GATT
if (device) {
const server = await device.gatt.connect();
const svc = await server.getPrimaryService(BLE_SERVICE);
const char = await svc.getCharacteristic(BLE_AUTH);
const bytes = new TextEncoder().encode(session.qrPayload);
await char.writeValueWithResponse(bytes);
server.disconnect();
}
// If extension is paired, it delivers automatically.
// 4. Wait for result (polling picks up the phone's VP POST)
auth.onVerified((result) => {
console.log('Signed in as', result.result.did);
});
};
</script>
Requirements:
- Direct BLE: Page must be served over HTTPS (Web Bluetooth requires a secure context). Works in Chrome and Edge.
- Extension bridge: The Hesskey Chrome extension must be installed and paired with the phone. No HTTPS requirement for the extension path.
- No server-side changes — the verifier sees a standard SIOP session regardless of transport
Light Client Trustless Verification
The Hesskey browser extension embeds a smoldot WASM light client that syncs directly with the Hesskey P2P network. DID resolution and signature verification happen locally in the browser — no trusted RPC node in between.
How it works
window.__hesskey.chain.resolveDid) to resolve the user's DID and fetch their on-chain authentication key — verified locally against the finalized state root
chainHead_v1_follow, offscreen document (MV3), state-proof verification, bundled chain spec
How to implement
The SDK auto-detects the extension's light client. If synced, it confirms the user's DID against on-chain state as a trustless cross-check. With a paired extension you can also skip the QR entirely via trySilentSignIn:
<script type="module">
import { HessKeyAuth, trySilentSignIn, isLightClientAvailable }
from 'https://your-verifier.com/shared/hesskey-sdk.js';
document.getElementById('signin').onclick = async () => {
// 1. Fast path: paired extension + Session Delegation.
// Signs a Session Auth Token locally — no QR, no phone.
const silent = await trySilentSignIn({
verifierApiUrl: 'https://your-verifier.com',
sessionInit: {
domain: window.location.hostname,
authType: 'web3',
},
});
if (silent?.status === 'verified') {
console.log('Silent sign-in:', silent.raw.result.did);
return;
}
// 2. Fallback: QR flow + trustless cross-check.
const auth = new HessKeyAuth({
verifierApiUrl: 'https://your-verifier.com',
domain: window.location.hostname,
authType: 'web3',
});
await auth.createSession();
auth.renderQR(document.getElementById('qr'), { size: 256 });
auth.onVerified(async (result) => {
// Cross-check the DID against the light client's chain state
let trustless = false;
if (isLightClientAvailable() && window.__hesskey?.chain) {
const resolved = await window.__hesskey.chain.resolveDid(
result.result.did
);
trustless = !!resolved;
}
console.log('Signed in, trustless =', trustless);
});
};
</script>
Requirements:
- The Hesskey Chrome extension (v0.5.2+) must be installed for the light client and the silent sign-in path
- Verifier must expose
POST /api/sessions/delegatedfortrySilentSignIn - The chain spec bundled with the extension determines which chain is the root of trust
- No server-side changes needed for the trustless cross-check — it runs entirely in the browser
<hesskey-login> Web Component
A drop-in custom element that gives any Hesskey page a complete sign-in / sign-out UX — extension-aware, smart-routed, session-persistent — in one HTML tag. Matches the Hesskey brand: purple border, orange icon & label, translucent green background.
How it works
<hesskey-login>. The element renders its own Sign In button, popover, and Sign Out button inside a shadow DOM so your page CSS can't accidentally style it.
authenticated with the DID. On sign out or extension disable, it dispatches logout / force-disconnect. Your page listens and reacts.
<script type="module">. Works in any page — admin portal, Foundation portal, plain static HTML.
How to implement
Add the module once and drop the tag anywhere you want a sign-in button:
<!-- Load the component once -->
<script type="module" src="/shared/hesskey-login.js"></script>
<!-- Drop the tag where you want the button to live -->
<hesskey-login
verifier-api="https://hesskey.org"
site-name="Foundation Portal"
session-key="foundation_session"></hesskey-login>
<script>
const login = document.querySelector('hesskey-login');
login.addEventListener('authenticated', (ev) => {
const did = ev.detail.did; // 32-char hex, no prefix
// render your signed-in UI
});
login.addEventListener('logout', () => {
// clear your signed-in UI
});
login.addEventListener('force-disconnect', (ev) => {
// user toggled pairing off in the extension — same as logout
// ev.detail.reason tells you who initiated it
});
</script>
Attributes:
verifier-api— base URL of the Hesskey verifier (defaults tolocation.origin)site-name— shown in the popover header (e.g. "Foundation Portal")session-key— localStorage key for the cached session (use a unique one per page to avoid collisions)
Events:
authenticated—detail: { did }. Fires on fresh sign-in or cached-session restore.logout— fired after the user signs out via the Sign Out button.force-disconnect—detail: { reason }. Fired when the extension broadcasts a teardown (e.g. user toggled pairing off in the extension popup).
Requirements:
- Verifier serves
/shared/hesskey-login.jsand/shared/hesskey-sdk.js(both shipped by default) - For the silent-sign-in path, the user must have the Hesskey Chrome extension installed and paired
- No build step; no framework; works in any page that can load ES modules
Styling:
The Sign In / Sign Out buttons use the Hesskey brand palette — purple border
(#7B1FA2), orange text & icon (#FF9800), and a
translucent green background (rgba(76,175,80,0.18)). All styling
lives inside the shadow DOM so page CSS can't override it.
Protected Application Pattern (SIOP v2)
The classic relying-party integration: a login gate, a Verifiable-Presentation sign-in, then a dashboard that greets the verified user. This exact pattern now powers the real Hesskey web app.
How it works
Add this to your website
The exact pattern this demo uses — a login gate plus a dashboard:
<button id="signin">Sign in with Hesskey</button>
<div id="qr"></div>
<script type="module">
import { HessKeyAuth }
from 'https://your-verifier.com/shared/hesskey-sdk.js';
const auth = new HessKeyAuth({
verifierApiUrl: 'https://your-verifier.com',
domain: window.location.hostname,
authType: 'siop_vp',
presentationDefinition: {
id: 'login',
inputDescriptors: [{
id: 'identity-credential',
name: 'Hesskey Identity',
purpose: 'Sign in to your dashboard',
constraints: {
fields: [
{ path: ['$.credentialSubject.givenName'] },
{ path: ['$.credentialSubject.familyName'] },
],
},
}],
},
});
document.getElementById('signin').onclick = async () => {
await auth.createSession();
auth.renderQR(document.getElementById('qr'), { size: 256 });
auth.onVerified((result) => {
const subject = result.credentials[0].credentialSubject;
console.log('Hello', subject.givenName, subject.familyName);
// ...show your dashboard
});
};
</script>
Server-side prerequisites:
- A running Hesskey verifier (Python FastAPI — see
hesskey-web) - Your verifier DID registered on-chain (free via the devnet faucet)
- The verifier validates VC issuer trust + signature for you — you only consume the
onVerifiedresult
Build on the Hesskey backbone
Hesskey is an identity + object backbone your site builds on. You read public state over a plain JSON API and write by composing an intent the user's phone approves and signs — your app never touches keys. The Hesskey web app at /web-app/ is the reference consumer: anything it does, your site can too.
foundation.hesskey.orgRead endpoints
| Endpoint | Returns |
|---|---|
GET /api/v1/orgs?did=<did> | spaces the DID owns/manages (id, name, role) |
GET /api/v1/orgs/{id}/events | events under an org (title, venue, start, capacity) |
GET /api/v1/orgs/{id}/members | on-chain Owners + Managers |
GET /api/v1/orgs/{id}/treasury | keyless org wallet (address, balance) |
GET /api/v1/events/{id}/tickets | tickets minted under an event |
GET /api/v1/holdings?key=<hex> | tickets held under an owner key |
Quickstart — sign in, read, write
import { HessKeyAuth }
from 'https://foundation.hesskey.org/shared/hesskey-sdk.js';
const auth = new HessKeyAuth({ verifierApiUrl: 'https://foundation.hesskey.org',
domain: location.hostname, authType: 'siop_vp', trust: 'local' });
await auth.createSession(); // silent or QR
auth.onVerified(async ({ result }) => {
const did = result.did.replace(/^did:hesskey:/, '');
// READ — the user's spaces, straight from the backbone
const { orgs } = await (await fetch(
`https://foundation.hesskey.org/api/v1/orgs?did=${did}`)).json();
// WRITE — grant Scan to door staff (Tier 3: the phone face-matches + signs)
const sat = await window.__hesskey.session.signChallenge(
{ origin: location.origin, challenge });
await fetch('https://foundation.hesskey.org/api/action', { method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ did,
intent: { call_index: 13, args: { object: orgs[0].id,
principal: { Person: doorStaffDid }, cap_bits: 0x10 } },
auth: { sdc: sat.sdc, sat: sat.sat, origin: location.origin } }) });
});