OIDC Integration Guide
This guide explains how an external application authenticates citizens using
the RDC / DRC platform. The platform exposes a standard OpenID Connect
provider, so any compliant OIDC/OAuth 2.0 client library works.
This page is the integrator-facing subset: what you need to build a
relying party. The internal mechanics of the provider (interactions, MMA,
consent, cookies) are documented in the internal engineering docs and are not
published here.
At a glance
| Item | Value |
|---|---|
| Provider (OP) | tri-ekyc — a standards-compliant OpenID Connect / OAuth 2.0 provider |
| Flow | Authorization Code + PKCE (S256) — the only supported flow |
| Login UI | drc-pass (citizen enters National ID → password → MFA → consent) |
| Token signing | RS256 (verify with the published JWKS) |
| UserInfo endpoint | Disabled — all claims are delivered in the ID token |
| Grant types | authorization_code, refresh_token |
1. Register your client
Client registration is static today — there is no public dynamic
registration endpoint. To onboard, request the platform team to add your client
to tri-ekyc/src/modules/oidc/configs/static-client.ts with:
| Field | Description |
|---|---|
client_id |
Unique identifier for your app |
client_secret |
Confidential secret (store server-side only) |
redirect_uris |
Exact callback URLs — the redirect_uri you send must match one of these exactly |
grant_types |
['authorization_code', 'refresh_token'] |
scope |
Space-separated scopes you are allowed to request (see §3) |
You will receive a client_id and client_secret per environment.
2. Discover the endpoints
Always read endpoints from the discovery document rather than hardcoding them:
GET {ISSUER_URL}/.well-known/openid-configuration
ISSUER_URL is {base}/api/oidc for the target environment:
| Environment | ISSUER_URL (example — confirm with platform team) |
|---|---|
| Local | https://localhost:3443/api/oidc |
| Develop | https://<develop-gateway>/api/oidc |
| Staging | https://<staging-gateway>/api/oidc |
| UAT | https://<uat-gateway>/api/oidc |
Key endpoints (also resolvable from discovery):
| Purpose | Endpoint |
|---|---|
| Discovery | GET /api/oidc/.well-known/openid-configuration |
| JWKS (verify tokens) | GET /api/oidc/jwks |
| Authorization | GET /api/oidc/auth |
| Token | POST /api/oidc/token |
| End session (logout) | GET|POST /api/oidc/session/end |
3. Scopes & claims
Request openid plus whatever identity data you need. Add offline_access to
receive a refresh token.
| Scope | Claims returned (in the ID token) |
|---|---|
openid |
sub, iss, aud, exp, iat |
email |
email, isEmailVerified |
phone |
phone, phoneCode, isPhoneVerified |
identity |
id, firstName, lastName, dob, gender, address, province, city, passCreateTime, passActiveTime, version, … |
offline_access |
Enables a refresh_token |
Because UserInfo is disabled, do not call a
/userinfoendpoint — read
claims straight from the verified ID token.
4. The flow
Your App tri-ekyc (OP) drc-pass (Login UI)
│ │ │
│── GET /api/oidc/auth ───────────>│ │
│ client_id, redirect_uri, │ │
│ scope, state, nonce, │── 302 ────────────────────>│
│ code_challenge (S256) │ citizen logs in │
│ │ (National ID → pwd → │
│ │ MFA → consent) │
│<── 302 {redirect_uri}?code&state ┤ │
│ │ │
│── POST /api/oidc/token ─────────>│ │
│ code, code_verifier, │ │
│ client_id+secret, redirect_uri │ │
│<── { id_token, access_token, │ │
│ refresh_token? } ───────────┤ │
│ │ │
│ verify id_token (RS256 via JWKS)│ │
│ check state + nonce │ │
Steps:
- Authorize — generate a PKCE
code_verifier+code_challenge(S256),
a randomstateandnonce; redirect the user to/api/oidc/auth. - Citizen authenticates — tri-ekyc redirects to drc-pass; the citizen enters
their National ID, password, MFA (OTP or face), and grants consent. - Callback — the OP redirects back to your
redirect_uriwithcodeand
state. Verifystatematches. - Token exchange —
POST /api/oidc/tokenwith thecode, the PKCE
code_verifier, your client credentials, and the sameredirect_uri. - Verify — validate the ID token signature (RS256 via JWKS),
iss,aud,
exp, andnonce. Read claims from it.
kyc_type (optional)
You may pass kyc_type on the authorize request to control required auth
methods:
kyc_type |
Required methods |
|---|---|
1 |
MFA only (skip password) |
| omitted / other | Password and MFA |
5. Reference implementation (Node.js, openid-client v6)
A complete working example lives in demo-app/ (Koa + openid-client v6). The
core is:
import * as oidc from 'openid-client';
// Discover + configure the client
const config = await oidc.discovery(
new URL(ISSUER_URL), // e.g. https://localhost:3443/api/oidc
CLIENT_ID,
CLIENT_SECRET,
);
// --- /login ---
const code_verifier = oidc.randomPKCECodeVerifier();
const code_challenge = await oidc.calculatePKCECodeChallenge(code_verifier);
const nonce = oidc.randomNonce();
const state = oidc.randomState();
// persist { code_verifier, nonce, state } in the user's session
const authUrl = oidc.buildAuthorizationUrl(config, {
redirect_uri: REDIRECT_URI,
scope: 'openid email identity phone', // add offline_access for refresh tokens
code_challenge,
code_challenge_method: 'S256',
state,
nonce,
});
// redirect the browser to authUrl
// --- /callback ---
const tokens = await oidc.authorizationCodeGrant(config, currentUrl, {
pkceCodeVerifier: code_verifier,
expectedNonce: nonce,
expectedState: state,
idTokenExpected: true,
});
const claims = tokens.claims(); // identity data from the ID token
Running locally against a self-signed cert? The demo sets
NODE_TLS_REJECT_UNAUTHORIZED=0. Never do this outside local dev.
To run the demo end-to-end:
# Provider + login UI
cd tri-ekyc && npm run local # OP on :3001 (SSL proxy :3443)
cd drc-pass && yarn dev # login UI on :9443
# Demo relying party
cd demo-app/examples/nodejs
npm install
# edit ../config.json (CLIENT_ID, CLIENT_SECRET, ISSUER_URL, REDIRECT_URI, SCOPES)
NODE_TLS_REJECT_UNAUTHORIZED=0 npm start # visit http://localhost:3080
6. Refresh tokens
Request the offline_access scope to receive a refresh_token. Exchange it at
the token endpoint with grant_type=refresh_token to obtain new tokens without
re-prompting the citizen. Store refresh tokens encrypted, server-side only.
7. Logout
End the citizen’s session at the provider via /api/oidc/session/end
(RP-initiated logout). Pass id_token_hint and an optional
post_logout_redirect_uri (must be pre-registered).
8. Security checklist
- Confidential client:
client_secretlives only on your server, never in the browser. - Always use PKCE (
S256) — required. - Validate
state(CSRF) andnonce(replay) on the callback. - Verify the ID token signature against the JWKS, plus
iss,aud,exp. -
redirect_uriandpost_logout_redirect_urimust be exact registered matches. - Use TLS everywhere; never disable cert validation outside local dev.
- Read claims from the ID token — there is no UserInfo endpoint.
Reference integrations in this repo
| Project | What it shows |
|---|---|
demo-app/ |
Minimal OIDC relying party (Koa + openid-client v6) |
rdc-trident-simulate/ |
Real portal logging citizens in via tri-ekyc OIDC (multi-env) |