Skip to main content
This guide adds credential verification to your web app. Your verification program defines the accepted credentials and requested claims. AIR Kit handles credential discovery, the consent screen, and presentation generation. Your backend issues a nonce and verifies the returned SD-JWT presentation.

Before you start

You need:
  • An AIR Developer Dashboard account and Partner ID, with verifier functionality enabled
  • A web app served over HTTPS, with its origin whitelisted under Account → Domains
  • A public HTTPS JWKS endpoint registered for your Partner ID
  • A server endpoint that signs short-lived Partner JWTs with scope: "verify"
  • A test holder with a matching credential from an issuer accepted by your program, or access to that issuer’s claim flow
  • A supported Node.js version for your web framework and AIR Kit
You do not need to operate an issuer backend or database to verify another issuer’s credentials. The issuance quickstart is optional. AIR login is required for this SDK flow; the code below includes it.

Step 1: Configure a verification program

  1. Open the Sandbox Developer Dashboard and copy your Partner ID from Account → General Settings.
  2. Under Verifier → Programs, create a program and select the accepted schema and issuer configuration.
  3. Select SD_JWT_VC as the accepted proof type and choose the claims to request.
  4. Publish or apply the program so it is active, and copy its verification program ID.
Predicate checks such as “age greater than 18” without revealing the value, and on-chain verification, need Iden3 credentials. See Iden3 credentials.

Step 2: Configure Partner authentication

The code below uses a Next.js app. Keep the private key on the server, including during local development.
Set your app’s environment variables:
Verification fails until AIR can fetch your JWKS. If you haven’t set one up for this Partner ID, follow JWKS endpoint. If you already issue credentials with the same Partner ID, reuse that JWKS and key pair. Your backend must know which user it issues a nonce to, so it can check that user’s nonce when the presentation comes back. The browser sends the user’s AIR login token as Authorization: Bearer <token>, and the backend validates it against AIR’s JWKS (see Sessions). This file does that, and stores one pending nonce per user:
lib/air-session.ts
Create a server endpoint for verification tokens:
app/api/verification-token/route.ts
This example uses your Partner ID as the JWKS key’s kid. If your registered key uses a different kid, use that value in the token header.

Step 3: Initialize AIR Kit and start verification

Call this helper from a browser event handler, such as your Verify button:
lib/verify-credential.ts
The nonce needs at least 128 bits of randomness, as in the token route above. If your flow needs to redirect users to an issuer when they lack a credential, add the optional redirectUrl pointing to that issuer’s claim page. During verification, AIR Kit:
  1. Finds matching credentials. If none exist, the user needs to complete an accepted issuer’s issuance flow before verification can succeed.
  2. Fetches and decrypts the credential on the holder’s device.
  3. Shows the requested data and asks the user to consent.
  4. Records consent and generates the presentation. Because you passed a nonce, the holder signs a key-binding JWT with their private signing key. It covers the credential, the disclosed claims, nonce, and programId, so a leaked presentation cannot be replayed.
  5. Returns the outcome to your app.
Handle both returned statuses and errors, including a cancelled flow. Grant access only after a successful result appropriate to your application’s trust requirements.

Step 5: Verify the presentation on your backend

The Compliant check runs in the browser, and the presentation wrapper is not signed. Your backend verifies the SD-JWT inside it: issuer signature, disclosures, expiry, credential type, and the key-binding JWT against the user’s nonce and your programId. Verify SD-JWT on your backend as lib/verify-air-sd-jwt.ts, then create the route:
app/api/verify-presentation/route.ts

Step 6: Test the integration

Run the test through your HTTPS tunnel URL, with the tunnel domain whitelisted under Account → Domains and the tunnel’s JWKS URL registered. Test with a matching credential, a credential that fails the requested condition, and a user who declines consent. Confirm your app handles missing credentials, expired tokens, and failed requests without granting access. Also confirm your backend rejects a replayed presentation (a second submit gets 400 No pending verification), a presentation made for another programId, and a presentation with a disclosure removed.

Troubleshooting

Next steps