Implementation Guide#
On this page
This guide walks you through implementing the complete OIDC4IA authentication flow with DIP.
Step 1: Create the PAR Request#
1.1 Generate PKCE Values#
Generate a code_verifier (43-128 character random string) and compute the code_challenge:
// Generate code_verifier (43-128 characters)
const codeVerifier = generateRandomString(43);
// Compute code_challenge = BASE64URL(SHA256(code_verifier))
const codeChallenge = base64url(sha256(codeVerifier));1.2 Generate State and Nonce#
const state = generateRandomString(32);
const nonce = generateRandomString(32);Store these values in your session for later verification.
1.3 Build the Client Assertion JWT#
The client assertion authenticates your client. See Authentication for full details.
Header:
{
"alg": "ES256",
"kid": "your-key-id",
"typ": "JWT"
}Payload:
{
"iss": "your_client_id",
"sub": "your_client_id",
"aud": "https://{dip-base-url}",
"exp": 1759835872
}1.4 Build the Request Object JWT#
The request object contains the authorization parameters, including the OIDC4IA claims parameter that specifies which verified identity data you need.
Header:
{
"alg": "ES256",
"kid": "your-key-id",
"typ": "JWT"
}Payload:
{
"iss": "your_client_id",
"sub": "your_client_id",
"aud": "https://{dip-base-url}",
"exp": 1759835872,
"client_id": "your_client_id",
"response_type": "code",
"redirect_uri": "https://your-app.com/callback",
"scope": "openid",
"state": "your-state-value",
"nonce": "your-nonce-value",
"code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"code_challenge_method": "S256",
"claims": { ... }
}Understanding the OIDC4IA Claims Request#
The claims parameter follows the OpenID Connect for Identity Assurance (OIDC4IA) specification (opens in a new tab). It allows you to request specific verified identity claims and document evidence.
The claims request has two main parts:
- 1
verification.evidence- Document details you want (e.g., document type, expiry date, issuer) - 2
claims- Identity claims you want (e.g., name, birthdate, nationality)
Minimal Example#
If you only need the user's given name, family name, and birthdate (no document details):
{
"claims": {
"id_token": {
"verified_claims": {
"verification": {
"trust_framework": { "value": "stoe" }
},
"claims": {
"given_name": { "essential": true },
"family_name": { "essential": true },
"birthdate": { "essential": true }
}
}
}
}
}Full Example#
If you need identity claims plus document verification details:
{
"claims": {
"id_token": {
"verified_claims": {
"verification": {
"trust_framework": { "value": "stoe" },
"evidence": [
{
"type": { "value": "document" },
"document_details": {
"type": null,
"document_number": null,
"date_of_expiry": null,
"issuer": null,
"personal_number": null
}
}
]
},
"claims": {
"given_name": { "essential": true },
"family_name": { "essential": true },
"birthdate": { "essential": true },
"nationalities": { "essential": true }
}
}
}
}
}For the complete list of available claims and document details, see the PAR Request documentation.
1.5 Send the PAR Request#
POST /par HTTP/1.1
Host: {dip-base-url}
Content-Type: application/x-www-form-urlencoded
client_id=your_client_id
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6...
&request=eyJhbGciOiJFUzI1NiIsImtpZCI6...1.6 PAR Response#
{
"request_uri": "urn:ietf:params:oauth:request_uri:550e8400-e29b-41d4-a716-446655440000",
"expires_in": 60
}Step 2: Redirect User to Authorization Endpoint#
Redirect the user to the authorization endpoint with the request_uri:
GET https://{dip-base-url}/auth
?client_id=your_client_id
&request_uri=urn:ietf:params:oauth:request_uri:550e8400-e29b-41d4-a716-446655440000The user is then handed off to the BankID app where they complete identity verification (document scanning and facial recognition).
Step 3: Handle the Callback#
After verification, the BankID app redirects the user back to your redirect_uri with an authorization code:
GET https://your-app.com/callback
?code=SplxlOBeZQQYbYS6WxSbIA
&state=your-state-value
&iss=https%3A%2F%2F{dip-base-url}Step 4: Exchange Code for Tokens#
4.1 Build a New Client Assertion#
Create a fresh client assertion JWT (same format as Step 1.3).
4.2 Send the Token Request#
POST /token HTTP/1.1
Host: {dip-base-url}
Content-Type: application/x-www-form-urlencoded
client_id=your_client_id
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6...
&grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&redirect_uri=https://your-app.com/callback4.3 Token Response#
{
"id_token": "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0...",
"access_token": "not_applicable",
"token_type": "Bearer",
"expires_in": 900
}Step 5: Decrypt and Validate the ID Token#
The id_token is a JWE (JSON Web Encryption) containing a signed JWT.
5.1 Decrypt the JWE#
Use your private key to decrypt the JWE and extract the signed JWT.
5.2 Verify the JWT Signature#
Fetch the DIP public keys from the JWKS endpoint and verify the signature.
5.3 Validate Claims#
Verify the following claims:
| Claim | Validation |
|---|---|
iss | Must match the DIP issuer URL |
aud | Must match your client_id |
exp | Must not be expired |
iat | Must not be in the future |
nonce | Must match the nonce from Step 1.2 |
5.4 Extract Verified Claims#
The user's verified identity data is in the verified_claims object. See ID Token for the complete structure.
Example ID Token payload:
{
"iss": "https://{dip-base-url}",
"sub": "hashed-subject-id",
"aud": "your_client_id",
"exp": 1759835872,
"iat": 1759832272,
"auth_time": 1759832272,
"nonce": "your-nonce-value",
"acr": "urn:bankid:idcheck",
"amr": ["face", "user"],
"verified_claims": {
"verification": {
"trust_framework": "stoe",
"evidence": [
{
"type": "document",
"check_details": [
{
"check_method": "vcrypt",
"result": "success"
},
{
"check_method": "data",
"organization": "id.politiet.no",
"result": "valid"
}
],
"document_details": {
"type": "passport",
"document_number": "AB1234567",
"date_of_issuance": "2020-01-15",
"date_of_expiry": "2030-01-15",
"issuer": {
"country_code": "NOR",
"name": "Politiet"
},
"personal_number": "12345678901"
}
}
]
},
"claims": {
"given_name": "AASAMUND",
"family_name": "OESTENBYEN",
"picture": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
"birthdate": "1990-01-15",
"gender": "male",
"nationalities": ["NOR"]
}
}
}Complete Flow Summary#
sequenceDiagram
participant Client
participant DIP
participant BankID as BankID App
participant User
Client->>DIP: 1. POST /par
DIP-->>Client: 2. request_uri
Client->>BankID: 3. Redirect to /auth
User->>BankID: 4. Complete verification
BankID->>DIP: 5. Request auth code
DIP-->>BankID: 6. Return auth code
BankID->>Client: 7. Redirect with code
Client->>DIP: 8. POST /token
DIP-->>Client: 9. Encrypted ID token
Client->>Client: 10. Decrypt and validate
Next Steps#
- Authentication - Detailed JWT requirements
- PAR Request - Full PAR documentation
- Token Request - Full token endpoint documentation
- ID Token - Complete ID token structure