Skip to main content

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_id and client_secret in a secrets manager (e.g. AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) — never in code, and never in .env files 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 401 after rotation as a deployment-ordering signal, not an iDen2 outage.

Offer and session hygiene​

  • Set the shortest offerTtlSeconds / ttlSeconds your 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, or authorizationUrl beyond 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_AUTHORIZED means 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 signatureValid is true and issuerTrusted is true, always also check revocationStatus !== "revoked" before granting access. A revocationStatus of "unknown" deserves a policy decision on your side — for high-stakes flows, treat unknown as not verified.
  • Log the checks[] array and issuerMemberId for 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 externalId values 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​

  • credentialData returned 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-jwt credentials, keep personal fields selectively disclosable so holders can practice data minimisation later. This is the default (omit selectiveDisclosure); only force fields to be always-visible when you have a concrete reason (SD-JWT & Selective Disclosure).

Quick reference​

DoDon't
HTTPS everywhere in productionCall iDen2 over HTTP outside staging tests
Secrets manager for credentialsCredentials in code, .env in git, client-side code
Shortest workable TTLsHour-long TTLs for attended flows
Check full verificationResult + revocationTrust status: "completed" alone
Confirm webhooks via GET /sessions/{id}Grant access straight from a callback payload
Opaque externalId referencesEmails or national IDs as externalId