Security Best Practices
A checklist of the practices every production iDen2 integration should follow.
Transport
- Always use HTTPS in production — never HTTP. TLS protects both your token requests and your API calls in transit. The staging environment may accept HTTP as an artifact of its configuration; production must not.
- Call the APIs from your backend only. Your
client_id,client_secret, and access tokens must never reach a browser, mobile app, or any client-side code.
Credential storage
- Store your
client_idandclient_secretin a secrets manager (e.g. AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) — never in code, and never in.envfiles committed to version control. - Cache access tokens in memory until shortly before they expire; never persist them to disk or logs.
- Scope access to those secrets to the services that actually call iDen2.
- Rotate the client secret if you suspect exposure, and treat a
401after rotation as a deployment-ordering signal, not an iDen2 outage.
Offer and session hygiene
- Set the shortest
offerTtlSeconds/ttlSecondsyour UX allows. The TTL bounds the window in which a leaked QR code or URL can be used. Attended flows rarely need more than 120–300 seconds. - Never log or store
qrCodeDataUrl,offerUrl, orauthorizationUrlbeyond their TTL. These artifacts are bearer-style: anyone who can scan or open them can act on the flow while it's pending. Strip them from request/response logging. - Create offers only in authenticated contexts —
PRE_AUTHORIZEDmeans you've already verified the user.
Validating verification results
status: "completed" alone is not an access decision. Before granting anything:
const result = session.verificationResult;
const grant =
session.status === 'completed' &&
result.signatureValid === true &&
result.issuerTrusted === true &&
result.policyCompliant === true &&
result.revocationStatus !== 'revoked';
- After
signatureValidistrueandissuerTrustedistrue, always also checkrevocationStatus !== "revoked"before granting access. ArevocationStatusof"unknown"deserves a policy decision on your side — for high-stakes flows, treat unknown as not verified. - Log the
checks[]array andissuerMemberIdfor audit.
Callbacks and correlation
- Never grant access on a webhook alone. Validate the payload, then confirm via
GET /sessions/{sessionId}with your own credentials before acting (Polling vs. Webhooks). - Validate
externalIdvalues on your side before trusting them in callbacks — look the reference up, confirm the record exists and expects a callback (Using externalId). - Don't put PII in
externalId; use opaque internal references.
Data handling
credentialDatareturned by Verification Gateway is personal data the holder consented to share with you, for this purpose. Store only what you need, for as long as you need it, under your applicable privacy obligations.- When issuing
vc+sd-jwtcredentials, keep personal fields selectively disclosable so holders can practice data minimisation later. This is the default (omitselectiveDisclosure); only force fields to be always-visible when you have a concrete reason (SD-JWT & Selective Disclosure).
Quick reference
| Do | Don't |
|---|---|
| HTTPS everywhere in production | Call iDen2 over HTTP outside staging tests |
| Secrets manager for credentials | Credentials in code, .env in git, client-side code |
| Shortest workable TTLs | Hour-long TTLs for attended flows |
Check full verificationResult + revocation | Trust status: "completed" alone |
Confirm webhooks via GET /sessions/{id} | Grant access straight from a callback payload |
Opaque externalId references | Emails or national IDs as externalId |