OIDC Automatic Configuration

Prerequisites

Before integrating Face Auth 2.0, you will need the following values from your Incode dashboard and OIDC configuration:

Value Description
AUTH_SERVER_URL Base URL of the OIDC authorization server (e.g. https://auth.demo.incode.com). Also used as the issuer value for ID token validation.
SERVER_URL Base URL of the Incode API server
CLIENT_ID Your registered OIDC client ID
REDIRECT_URI The redirect URI registered with your OIDC client
API_KEY Your Incode API key
ADMIN_EMAIL Admin account email for the Incode Admin API
ADMIN_PASSWORD Admin account password for the Incode Admin API

🚧

Keep credentials secure

Never expose ADMIN_EMAIL, ADMIN_PASSWORD, or API_KEY in client-side code. All requests using these values must be made from your server.

Step 1: Generate PKCE Parameters

Purpose: Generate all cryptographic values needed for a secure OIDC authorization request.

Before redirecting the user to the authorization server, your application must generate the following values:

Value How it's generated
nonce 16 random bytes, base64url-encoded
state 16 random bytes, base64url-encoded
code_verifier 32 random bytes, base64url-encoded (must be 43–128 chars, per PKCE spec)
code_challenge SHA-256 hash of code_verifier, base64url-encoded

Store these values server-side — you will need code_verifier, nonce, and state in later steps.

Example (JavaScript)

const crypto = require('crypto');

function base64UrlEncode(buffer) {
  return buffer.toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

const nonce        = base64UrlEncode(crypto.randomBytes(16));
const state        = base64UrlEncode(crypto.randomBytes(16));
const codeVerifier = base64UrlEncode(crypto.randomBytes(32));
const codeChallenge = base64UrlEncode(
  crypto.createHash('sha256').update(codeVerifier).digest()
);

Authorization URL structure:

GET {AUTH_SERVER_URL}/oauth2/authorize
  ?client_id={CLIENT_ID}
  &redirect_uri={REDIRECT_URI}
  &scope=openid
  &response_type=code
  &response_mode=form_post
  &code_challenge_method=S256
  &code_challenge={code_challenge}
  &state={state}
  &nonce={nonce}

🚧

Persist your code_verifier

The code_verifier must be securely stored (e.g. in a server-side session) until the token exchange in Step 3. Do not regenerate or overwrite it between steps.

Step 2: Redirect the User to the Authorization URL

Purpose: Send the user to the authorization server to complete face authentication.

Using the authorization URL built in Step 1, redirect the user's browser to begin the face authentication flow. The authorization server will guide the user through:

  1. Liveness detection
  2. ID document capture and validation
  3. Face recognition (matching selfie to document photo)

Example (JavaScript)

const params = new URLSearchParams({
  client_id:             CLIENT_ID,
  redirect_uri:          REDIRECT_URI,
  scope:                 'openid',
  response_type:         'code',
  response_mode:         'form_post',
  code_challenge_method: 'S256',
  code_challenge:        codeChallenge,
  state:                 state,
  nonce:                 nonce,
});

const authorizeUrl = `${AUTH_SERVER_URL}/oauth2/authorize?${params.toString()}`;

// Redirect the user
window.location.href = authorizeUrl;

On success, the authorization server will POST to your REDIRECT_URI with the following form-encoded fields:

Field Description
code A short-lived authorization code to exchange for tokens
state The state value you sent — verify this matches what you stored in Step 1

📘

response_mode=form_post

The code and state are delivered as a POST to your REDIRECT_URI, not as URL query parameters. Make sure your redirect endpoint can receive a form-encoded POST body.

🚧

Validate state on return

Always verify that the state value returned by the server matches the one you generated in Step 1. A mismatch may indicate a CSRF attack.

Step 3: Exchange the Authorization Code for Tokens

Endpoint: POST {AUTH_SERVER_URL}/oauth2/token

Once your redirect endpoint receives the code, your server must exchange it for an access token and ID token.

Request

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
Accept: application/json
Parameter Value
grant_type authorization_code
client_id YOUR_CLIENT_ID
redirect_uri YOUR_REDIRECT_URI
code The authorization code received from Step 2
code_verifier The code_verifier generated in Step 1

Example (JavaScript)

const response = await fetch(`${AUTH_SERVER_URL}/oauth2/token`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type:    'authorization_code',
    client_id:     CLIENT_ID,
    redirect_uri:  REDIRECT_URI,
    code:          authorizationCode,
    code_verifier: codeVerifier,
  }),
});

const tokens = await response.json();

Response

{
  "access_token": "<JWT>",
  "id_token": "<JWT>",
  "scope": "openid",
  "token_type": "Bearer",
  "expires_in": 3600
}

ID Token Validation

After receiving the ID token, your application is responsible for validating it before trusting any of its claims. Use a well-maintained JWT library rather than implementing validation manually.

Recommended libraries:

Language Library
JavaScript / Node.js jose, jsonwebtoken
Python python-jose, authlib
Java nimbus-jose-jwt
Go golang-jwt/jwt
Ruby jwt

Claims to validate:

Claim Expected Value Why
iss Must exactly match your AUTH_SERVER_URL Confirms the token was issued by the authorization server you trust
exp Must be in the future (compare against server time) Rejects expired tokens
nonce Must match the nonce generated in Step 1 Confirms the token was issued in response to your specific request — protects against replay attacks
azp Must equal CLIENT_ID when multiple audiences are present Confirms your app is the intended recipient of the token
kid Must be present in the JWKS at {AUTH_SERVER_URL}/oauth2/jwks Used to verify the token's cryptographic signature

Verifying the token signature (JWKS):

The kid value in the token header identifies which public key to use for signature verification. Fetch Incode's public keys and find the matching key:

GET {AUTH_SERVER_URL}/oauth2/jwks

Example (JavaScript using jose)

import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL(`${AUTH_SERVER_URL}/oauth2/jwks`)
);

const { payload } = await jwtVerify(idToken, JWKS, {
  issuer:   AUTH_SERVER_URL,
  audience: CLIENT_ID,
});

// Validate nonce manually
if (payload.nonce !== storedNonce) {
  throw new Error('Nonce mismatch — possible replay attack');
}

Codes are single-use

Authorization codes expire quickly (typically 60 seconds) and can only be exchanged once. If the token exchange fails, the user will need to restart the authentication flow from Step 1.

Step 4: Retrieve the Session / Interview ID

Endpoint: GET {AUTH_SERVER_URL}/userinfo

Once your application has a valid access token, call the OIDC UserInfo endpoint to retrieve the user's session details. The sub claim returned contains the Incode session identifier, referred to as interviewId, which is required to retrieve verification results in Step 5b.

Request

GET /userinfo
Authorization: Bearer YOUR_ACCESS_TOKEN

Example (JavaScript)

const response = await fetch(`${AUTH_SERVER_URL}/userinfo`, {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
  },
});

const userInfo = await response.json();
const interviewId = userInfo.sub;

Response (example)

{
  "sub": "667ce6cd1f9dce127eabe35e",
  "email": "user@example.com"
}
Field Description
sub The Incode session identifier (interviewId) — store this for Step 5b
email The authenticated user's email address

📘

Store the interviewId

The sub value returned here is your key to retrieving the full verification score. Store it securely on your server before proceeding to Step 5.

Step 5a: Create an Admin Token

Endpoint: POST {SERVER_URL}/executive/log-in

Before retrieving the verification score, your server must authenticate with the Incode Admin API to obtain a short-lived, high-privilege admin token.

Request

POST /executive/log-in
Content-Type: application/json
api-version: 1.0
x-api-key: YOUR_API_KEY

JSON

{
  "email": "YOUR_ADMIN_EMAIL",
  "password": "YOUR_ADMIN_PASSWORD"
}

Response

{
  "token": "<admin-token>"
}
Field Description
token Short-lived admin token required for the score retrieval request in Step 5b

Step 5b: Retrieve the Verification Score

Endpoint: GET {SERVER_URL}/omni/get/score

Using the interviewId from Step 4 and the adminToken from Step 5a, your server can now retrieve the full biometric verification result for the session.

Request

GET /omni/get/score?id=YOUR_INTERVIEW_ID
Content-Type: application/json
api-version: 1.0
x-api-key: YOUR_API_KEY
X-Incode-Hardware-Id: YOUR_ADMIN_TOKEN
Query Parameter Required Description
id No The interviewId from Step 4. If omitted, the interview ID is extracted from the authentication token.

Example (JavaScript)

const response = await fetch(
  `${SERVER_URL}/omni/get/score?id=${interviewId}`,
  {
    method: 'GET',
    headers: {
      'Content-Type':         'application/json',
      'api-version':          '1.0',
      'x-api-key':            API_KEY,
      'X-Incode-Hardware-Id': adminToken,
    },
  }
);

const score = await response.json();

Response

A successful response returns a 200 OK with a score object containing the following top-level sections:

Section Description
overall Composite score and pass/fail status across all checks
idValidation Document authenticity and quality checks
liveness Liveness detection results
faceRecognition Selfie-to-ID face match results
deepsight Multimodal fraud intelligence signals
idOcrConfidence Confidence scores for extracted text fields
governmentValidation Government database cross-check results
watchlistScore Watchlist screening results
antifraud Aggregate fraud risk assessment
deviceRisk Device trust signals
behavioralRisk Behavioral trust signals
reasonMsg Human-readable explanation of the overall outcome

For the full field definitions, status values, and schema for each section, refer to the Fetch Scores API reference.