function mapClaims({ user = {}, ialData = {}, userDocuments = [], mapping = [], workflowSummary = null, errorContext = null }) {
const claims = {};
// Helper function to get nested values (e.g., 'aliases.alias1')
const getNestedValue = (obj, path) => {
return path.split('.').reduce((nestedData, nestedKey) => nestedData && nestedData[nestedKey], obj);
};
// Process each claim in the mapping (skip if mapping is not provided for IDV flow)
if (mapping && mapping.length > 0) {
for (const { claim_name, attribute_name, attribute_type, value_type, value } of mapping) {
let attrValue;
if (value_type === 'null') {
claims[claim_name] = null;
continue;
}
if (value_type === 'static') {
claims[claim_name] = value;
continue;
}
if (attribute_type === 'session') {
if (attribute_name === 'aal') {
attrValue = ialData.aal;
} else {
attrValue = getNestedValue(user, attribute_name);
}
if (value_type === 'default') {
attrValue = attrValue || value;
}
}
else if (attribute_type === 'ledger') {
if (attribute_name === 'ial') {
attrValue = ialData.ial;
} else {
attrValue = (ialData.authenticator_data && ialData.authenticator_data[attribute_name]) ||
(ialData.pon_data && ialData.pon_data[attribute_name]);
}
if (value_type === 'default') {
attrValue = attrValue || value;
}
}
else if (attribute_type === 'identity') {
const attributeSegments = attribute_name.split('.');
if (attributeSegments.length === 2) {
const [docType, property] = attributeSegments;
const document = userDocuments.find((doc) => doc.type === docType);
if (document) {
attrValue = document.data[property];
}
}
else if (attributeSegments.length === 3) {
const [docType, nestedObj, property] = attributeSegments;
const document = userDocuments.find((doc) => doc.type === docType);
if (document) {
attrValue = document.data[nestedObj] && document.data[nestedObj][property];
}
}
if (value_type === 'default') {
attrValue = attrValue || value;
}
}
if (attrValue === undefined) {
throw new Error(`${attribute_type}.${attribute_name} cannot be prepared, because data does not contain this attribute`);
}
claims[claim_name] = typeof attrValue === 'object' ? JSON.stringify(attrValue) : attrValue;
}
}
const createVerifiedClaims = () => {
const expectedClaims = workflowSummary?.metaData?.claims?.id_token?.verified_claims?.[0]?.claims || {};
const personInfo = workflowSummary?.summary?.person_info || {};
if (errorContext) {
const failedClaims = {};
if (expectedClaims.given_name) {
failedClaims.given_name = expectedClaims.given_name?.fuzzy ? { value: null, fuzzy: true } : null;
}
if (expectedClaims.family_name) {
failedClaims.family_name = expectedClaims.family_name?.fuzzy ? { value: null, fuzzy: true } : null;
}
if (expectedClaims.birthdate) {
failedClaims.birthdate = expectedClaims.birthdate?.fuzzy ? { value: null, fuzzy: true } : null;
}
return [{
verification: {
assurance_level: "FAILED",
time: new Date().toISOString(),
trust_framework: "IDV-DELEGATED",
verification_process: "idv-session-" + Date.now()
},
claims: failedClaims
}];
}
if (Object.keys(expectedClaims).length === 0) {
return [];
}
let allClaimsVerified = true;
const verifiedClaimsObj = {};
const buildVerifiedClaim = (actualValue, expectedClaim) => {
if (actualValue) {
if (expectedClaim?.fuzzy) {
return { value: actualValue, fuzzy: true };
} else {
return actualValue;
}
} else {
allClaimsVerified = false;
if (expectedClaim?.fuzzy) {
return { value: null, fuzzy: true };
} else {
return null;
}
}
};
if (expectedClaims.given_name) {
verifiedClaimsObj.given_name = buildVerifiedClaim(personInfo.firstName, expectedClaims.given_name);
}
if (expectedClaims.family_name) {
verifiedClaimsObj.family_name = buildVerifiedClaim(personInfo.lastName, expectedClaims.family_name);
}
if (expectedClaims.birthdate) {
verifiedClaimsObj.birthdate = buildVerifiedClaim(personInfo.dob, expectedClaims.birthdate);
}
const assuranceLevel = allClaimsVerified ? "VERIFIED" : "FAILED";
return [{
verification: {
assurance_level: assuranceLevel,
time: new Date().toISOString(),
trust_framework: "IDV-DELEGATED",
verification_process: "idv-session-" + Date.now()
},
claims: verifiedClaimsObj
}];
};
if (workflowSummary || errorContext) {
claims.verified_claims = createVerifiedClaims();
}
return claims;
}
Log in to your tenant as a Community Administrator.Navigate to Applications > Add Application (or click Add an application from the Manage Applications page).Scroll down and click Add Integration under the OIDC tile.In the Create new OIDC Integration page, fill in the following:Application name — Descriptive name, for example: Okta IDV for Account RecoveryGrant Type — Authorization CodeSign-In Redirect URI — Format: https://{okta-domain}/idp/identity-verification/callback Example: https://company.okta.com/idp/identity-verification/callbackScopes — The following scopes are required:profile — Required for user profile claims (enabled by default)openid — Required for OIDC authentication (enabled by default)identity_assurance — Required — enables the identity verification flowidv_flow_{id} — Required — specifies which workflow to trigger. Replace {id} with the workflow access code.Example: if access code is abc123, scope is idv_flow_abc123Example: if access code is verify-user-2024, scope is idv_flow_verify-user-2024Configure the Claims Transformation Script (see below).Click Create. A Client ID and Client Secret are automatically assigned.
Scopes Behavior
When identity_assurance is selected, the related idv_flow_{id} scope is automatically populated, enabled by default, and non-editable. When identity_assurance is unselected, idv_flow_{id} is automatically removed.
Enabling and Configuring the Claims Transformation Script
In the OIDC application configuration, navigate to Claims TransformationToggle Returns a custom JSON payload to ON — a sample script appears in the code editorThe script receives these inputs:user — user session attributes (e.g., firstname, lastname, email)ialData — identity assurance and authenticator datamapping — claim mappings derived from selected scopesworkflowSummary (optional) — verification workflow resultsThe sample script is commented out by default. To activate it:Select all commented lines (starting with //)macOS: Cmd+/ | Windows: Ctrl+/Or manually remove // from the beginning of each lineAfter uncommenting, you will be prompted for OTP verification (Email or SMS). Enter the 6-digit OTP to confirm and save.
Note: The Run feature in the script editor validates syntax and behavior in isolation — it does not execute the full end-to-end identity flow. Use it to check for errors before saving.
Supported Claims for Identity Proofing
The current identity proofing flow supports comparison of the following claims only:
given_name (First Name)family_name (Last Name)birthdate (Date of Birth)
Okta may send additional claims depending on its configuration, but only the three attributes above are processed for identity proofing purposes.
Sample Claims Transformation Script
function mapClaims({ user = {}, ialData = {}, userDocuments = [], mapping = [], workflowSummary = null, errorContext = null }) { const claims = {}; // Helper function to get nested values (e.g., 'aliases.alias1') const getNestedValue = (obj, path) => { return path.split('.').reduce((nestedData, nestedKey) => nestedData && nestedData[nestedKey], obj); }; // Process each claim in the mapping (skip if mapping is not provided for IDV flow) if (mapping && mapping.length > 0) { for (const { claim_name, attribute_name, attribute_type, value_type, value } of mapping) { let attrValue; if (value_type === 'null') { claims[claim_name] = null; continue; } if (value_type === 'static') { claims[claim_name] = value; continue; } if (attribute_type === 'session') { if (attribute_name === 'aal') { attrValue = ialData.aal; } else { attrValue = getNestedValue(user, attribute_name); } if (value_type === 'default') { attrValue = attrValue || value; } } else if (attribute_type === 'ledger') { if (attribute_name === 'ial') { attrValue = ialData.ial; } else { attrValue = (ialData.authenticator_data && ialData.authenticator_data[attribute_name]) || (ialData.pon_data && ialData.pon_data[attribute_name]); } if (value_type === 'default') { attrValue = attrValue || value; } } else if (attribute_type === 'identity') { const attributeSegments = attribute_name.split('.'); if (attributeSegments.length === 2) { const [docType, property] = attributeSegments; const document = userDocuments.find((doc) => doc.type === docType); if (document) { attrValue = document.data[property]; } } else if (attributeSegments.length === 3) { const [docType, nestedObj, property] = attributeSegments; const document = userDocuments.find((doc) => doc.type === docType); if (document) { attrValue = document.data[nestedObj] && document.data[nestedObj][property]; } } if (value_type === 'default') { attrValue = attrValue || value; } } if (attrValue === undefined) { throw new Error(`${attribute_type}.${attribute_name} cannot be prepared, because data does not contain this attribute`); } claims[claim_name] = typeof attrValue === 'object' ? JSON.stringify(attrValue) : attrValue; } } const createVerifiedClaims = () => { const expectedClaims = workflowSummary?.metaData?.claims?.id_token?.verified_claims?.[0]?.claims || {}; const personInfo = workflowSummary?.summary?.person_info || {}; if (errorContext) { const failedClaims = {}; if (expectedClaims.given_name) { failedClaims.given_name = expectedClaims.given_name?.fuzzy ? { value: null, fuzzy: true } : null; } if (expectedClaims.family_name) { failedClaims.family_name = expectedClaims.family_name?.fuzzy ? { value: null, fuzzy: true } : null; } if (expectedClaims.birthdate) { failedClaims.birthdate = expectedClaims.birthdate?.fuzzy ? { value: null, fuzzy: true } : null; } return [{ verification: { assurance_level: "FAILED", time: new Date().toISOString(), trust_framework: "IDV-DELEGATED", verification_process: "idv-session-" + Date.now() }, claims: failedClaims }]; } if (Object.keys(expectedClaims).length === 0) { return []; } let allClaimsVerified = true; const verifiedClaimsObj = {}; const buildVerifiedClaim = (actualValue, expectedClaim) => { if (actualValue) { if (expectedClaim?.fuzzy) { return { value: actualValue, fuzzy: true }; } else { return actualValue; } } else { allClaimsVerified = false; if (expectedClaim?.fuzzy) { return { value: null, fuzzy: true }; } else { return null; } } }; if (expectedClaims.given_name) { verifiedClaimsObj.given_name = buildVerifiedClaim(personInfo.firstName, expectedClaims.given_name); } if (expectedClaims.family_name) { verifiedClaimsObj.family_name = buildVerifiedClaim(personInfo.lastName, expectedClaims.family_name); } if (expectedClaims.birthdate) { verifiedClaimsObj.birthdate = buildVerifiedClaim(personInfo.dob, expectedClaims.birthdate); } const assuranceLevel = allClaimsVerified ? "VERIFIED" : "FAILED"; return [{ verification: { assurance_level: assuranceLevel, time: new Date().toISOString(), trust_framework: "IDV-DELEGATED", verification_process: "idv-session-" + Date.now() }, claims: verifiedClaimsObj }]; }; if (workflowSummary || errorContext) { claims.verified_claims = createVerifiedClaims(); } return claims;}