A working "Continue with Human Pass" in six steps, in Node.js with openid-client. Using a framework? See the Next.js and Express guides, or any OIDC library.
1. Get credentials
- Sign in to the dashboard, or create an account if you don't have one yet: the sandbox is open to everyone.
- Create a project: usually one per product. Every client in a project gets the same ID for a person (why).
- Create a client with your callback address as its redirect URI, for example
http://localhost:3000/auth/callback. Choose server-side if your app has a backend: you get a client secret, shown once.
HP_ISSUER=https://id.sandbox.humanpass.co
HP_CLIENT_ID=hp_test_…
HP_CLIENT_SECRET=hps_…
HP_REDIRECT_URI=http://localhost:3000/auth/callback2. Install an OIDC library
npm install openid-client3. Send people to Human Pass
Discover Human Pass's settings once at start-up, then build an authorization request. PKCE is required, and ID tokens are signed with ES256.
import * as oidc from "openid-client";
export const config = await oidc.discovery(
new URL(process.env.HP_ISSUER!),
process.env.HP_CLIENT_ID!,
{ id_token_signed_response_alg: "ES256" },
oidc.ClientSecretBasic(process.env.HP_CLIENT_SECRET!),
);
/** Returns where to send the browser, and what to keep in the session until it comes back. */
export async function startSignIn() {
const verifier = oidc.randomPKCECodeVerifier();
const state = oidc.randomState();
const nonce = oidc.randomNonce();
const url = oidc.buildAuthorizationUrl(config, {
redirect_uri: process.env.HP_REDIRECT_URI!,
scope: "openid personhood",
code_challenge: await oidc.calculatePKCECodeChallenge(verifier),
code_challenge_method: "S256",
state,
nonce,
});
return { url, session: { verifier, state, nonce } };
}4. Handle the callback
Exchange the code for tokens. The library checks the state, nonce, PKCE verifier and the ID token's signature.
export async function finishSignIn(callbackUrl: URL, session: { verifier: string; state: string; nonce: string }) {
const tokens = await oidc.authorizationCodeGrant(config, callbackUrl, {
pkceCodeVerifier: session.verifier,
expectedState: session.state,
expectedNonce: session.nonce,
idTokenExpected: true,
});
const claims = tokens.claims()!;
if (claims.verified_human !== true) throw new Error("not a verified person");
return { sub: claims.sub, previousSub: claims.previous_sub as string | undefined };
}5. One person, one account
Key your accounts on sub. Store it as an opaque string of up to 255 characters. If the token also has previous_sub, the person's ID was rotated: move their account to the new sub (details).
async function accountFor(sub: string, previousSub?: string) {
return db.transaction(async (tx) => {
const existing = await tx.accounts.findBySub(sub);
if (existing) return existing;
if (previousSub) {
const old = await tx.accounts.findBySub(previousSub);
if (old) return tx.accounts.update(old.id, { sub }); // same person, new ID
}
return tx.accounts.create({ sub }); // a new person
});
}6. Check for changes
IDs can be suspended, deleted or merged after someone signs in. Ask the status API regularly (for example daily) about the IDs you hold, and act on anything that isn't active.
curl -u "$HP_CLIENT_ID:$HP_CLIENT_SECRET" \
-H "content-type: application/json" \
-d '{"subs": ["1.Ab3x…"]}' \
https://id.sandbox.humanpass.co/v1/subjects/statusThat's it. Try it with the test personas, then read going live.