KRDPass Integration Guide
Version 1.1 — December 2025
1. Introduction
KRDPass enables secure, passwordless citizen authentication for digital services within the Kurdistan Region. Authentication is performed exclusively through the Central Authentication Service (CAS) of the DIT using OpenID Connect (OIDC).
Your application never handles passwords. CAS handles all authentication through the KRDPass mobile app using a QR-code approval flow. Your backend then receives an OIDC authorization code and exchanges it securely for tokens.
This guide outlines the correct and compliant integration pattern.
2. How KRDPass Login Works (High-Level)
- User tries to access a protected page.
- Your backend redirects them to CAS via OIDC.
- CAS displays:
- a QR code
- a numeric approval code
- User opens KRDPass mobile app → approves login.
- CAS sends the user back to your application with an authorization code.
- Your backend exchanges this code for tokens.
- Backend creates a secure session via HttpOnly cookies.
- User is authenticated inside your system.
3. Required OIDC Configuration
| Setting | Description | Example |
|---|---|---|
Authority | CAS issuer | https://account.id.krd |
ClientId | Application ID | your-client-id |
ClientSecret | Application secret | your-client-secret |
CallbackPath | Redirect URI path | /auth/callback |
Mandatory parameters
response_type=code
scope=openid profile
acr_values=idp:urn:krg:identity:citizenThe acr_values ensure CAS uses the KRDPass Citizen Identity Provider.
4. Identity Claims Returned by CAS
Example claims:
{
"iss": "https://account.id.krd",
"sub": "77ad7707bb007377cb07377",
"upn": "5010219103805",
"upns": ["5010219103805"],
"did": "8D00D48F-6F97-436D-954B-F14709DCE3AC",
"dpv": 1
}Identity Handling Requirements (MANDATORY)
1. sub (primary key)
- Must be stored
- Must be used as your internal primary key
- Must never be displayed in UI, logs, receipts, or communications
- Used only for identity mapping and session association
2. upn (citizen identifier)
- Must be stored
- Must be displayed in all citizen-facing or administrative interfaces
- Used as the primary searchable citizen reference across all systems
3. upns (historical UPNs)
- Must be stored
- Must never be displayed
- Used for audit, data reconciliation, and recovery scenarios
Identity Summary Table
| Claim | Store? | Display? | Purpose |
|---|---|---|---|
sub | ✔ Yes | ❌ No | Internal PK |
upn | ✔ Yes | ✔ Yes | Citizen-facing identifier |
upns | ✔ Yes | ❌ No | Historical audit & search |
5. Security Architecture Requirements
KRDPass requires a high-security OIDC integration model.
5.1 Tokens Must Never Be Accessible to the Frontend
Prohibited:
- ID token in JavaScript
- Access tokens in localStorage/sessionStorage
- Authorization code in browser memory
- Tokens in cookies
All tokens must remain backend-only.
5.2 Authorization Code Must Never Reach the Browser
Authorization codes may only be handled in the backend.
5.3 Sessions Must Be HttpOnly
Your session cookies must be:
HttpOnlySecureSameSite=Lax- Rotated on login
6. Mandatory Architecture: Backend-for-Frontend (BFF)
All new systems integrating with KRDPass must use a BFF pattern.
A BFF:
- Performs the OIDC flow entirely server-side
- Exchanges the authorization code for tokens
- Stores tokens securely server-side
- Issues only safe cookies to the browser
- Exposes minimal endpoints:
POST /session/loginPOST /session/logoutGET /session/me(basic profile only)
The SPA (React/Vue/Angular/etc.) must never:
- Communicate with CAS directly
- Store tokens
- Receive tokens
- Perform OIDC itself
7. DIT-Provided Standard BFF (Important)
For services hosted inside DIT infrastructure:
DIT provides an official, hardened BFF component.
It:
- Implements the OIDC Authorization Code Flow correctly
- Stores tokens securely
- Issues safe session cookies
- Prevents any token exposure
- Ensures full compliance with KRDPASS authentication policy
How to obtain it:
👉 Contact the Head of DevOps at DIT. You’ll receive:
- Containerized BFF component (Docker image / Helm chart)
- Deployment instructions
- Integration templates
- Routing and environment configuration support
This is the recommended and compliant method for all DIT-hosted platforms.
8. COMPLIANCE REQUIREMENTS
The following rules are mandatory for any system integrating with KRDPass.
8.1 Authentication Implementation
- Must use standard OpenID Connect Authorization Code Flow
- Must include
acr_values=idp:urn:krg:identity:citizen - Do NOT use the OIDC Implicit Flow, Hybrid Flow, or SPA-only flows
- Do NOT build your own OAuth/OIDC implementation
- Must use a standard, battle-tested OIDC/OAuth2 library for your stack
- Example libraries:
- .NET: Microsoft.Owin / ASP.NET OIDC Middleware
- Node.js:
openid-client - PHP:
jumbojett/openid-connect-php - Ruby:
omniauth-openid-connect - Java: Spring Security OAuth2 Client
- Go:
coreos/go-oidc
- No custom token parsing, signature verification, or OIDC protocol reimplementation
Reinventing OIDC or OAuth2 is explicitly prohibited. This ensures:
- correct security
- protocol compliance
- maintainability
- interoperability and correctness during audits
8.2 Identity Handling
- Store
subas internal primary key - Store
upnas external citizen identifier - Store
upnsfor audit - Never display
suborupns
8.3 Token and Session Security
- All tokens processed server-side only
- No token storage in front-end code
- No token exposure in logs
- Session cookies must be
HttpOnly,Secure,SameSite=Strict
8.4 Architecture
- Must use a BFF architecture
- SPA must communicate only with BFF, not CAS
- Must use DIT’s official BFF if hosted in DIT infrastructure
8.5 Validation
- Validate
iss=https://account.id.krd - Validate
aud(client_id) - Validate expiry,
nonce, andstate - Reject authentication from unknown or mismatched clients
9. Backend Integration Examples
Below are examples using widely adopted, compliant OIDC libraries.
9.1 .NET (ASP.NET Core)
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://account.id.krd";
options.ClientId = config["KRDPass:ClientId"];
options.ClientSecret = config["KRDPass:ClientSecret"];
options.CallbackPath = "/auth/callback";
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.SaveTokens = false;
options.Events.OnRedirectToIdentityProvider = ctx =>
{
ctx.ProtocolMessage.AcrValues = "idp:urn:krg:identity:citizen";
return Task.CompletedTask;
};
});9.2 PHP (openid-connect-php)
$oidc = new OpenIDConnectClient(
'https://account.id.krd',
getenv('KRD_CLIENT_ID'),
getenv('KRD_CLIENT_SECRET')
);
$oidc->setRedirectURL('https://your-app.com/auth/callback');
$oidc->addScope('openid');
$oidc->addScope('profile');
$oidc->addAuthParam('acr_values', 'idp:urn:krg:identity:citizen');
$oidc->authenticate();
$sub = $oidc->requestUserInfo('sub');
$upn = $oidc->requestUserInfo('upn');
$upns = $oidc->requestUserInfo('upns');
store_identity($sub, $upn, $upns);9.3 Ruby (OmniAuth)
provider :openid_connect, {
name: :krdpass,
scope: [:openid, :profile],
response_type: :code,
issuer: 'https://account.id.krd',
discovery: true,
client_options: {
identifier: ENV['KRD_CLIENT_ID'],
secret: ENV['KRD_CLIENT_SECRET'],
redirect_uri: 'https://your-app.com/auth/krdpass/callback'
},
authorize_params: {
acr_values: 'idp:urn:krg:identity:citizen'
}
}9.4 Node.js (openid-client)
const authUrl = client.authorizationUrl({
scope: 'openid profile',
state,
nonce,
acr_values: 'idp:urn:krg:identity:citizen'
});Callback:
const tokenSet = await client.callback(redirectUri, params);
const user = await client.userinfo(tokenSet.access_token);
saveIdentity({
sub: user.sub,
upn: user.upn,
upns: user.upns
});9.5 Java (Spring Security)
spring:
security:
oauth2:
client:
registration:
krdpass:
client-id: ${KRD_CLIENT_ID}
client-secret: ${KRD_CLIENT_SECRET}
scope: openid,profile
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
provider:
krdpass:
issuer-uri: https://account.id.krdAdd acr_values:
return OAuth2AuthorizationRequest.from(req)
.additionalParameters(params -> params.put("acr_values", "idp:urn:krg:identity:citizen"))
.build();