Identity
The IDK identity framework manages the lifecycle of binding external subjects (people, organizations, or workloads) to internal identities. It answers the questions: "Who is this external subject?", "Have we seen them before?", "Do we trust the evidence they present?", and "What level of assurance do we have?"
The framework sits at the intersection of several IDK subsystems. Decentralized Identifiers provide the subject identifiers. Identifier Resolution turns those identifiers into cryptographic key material for signature verification. Trust Establishment validates whether the issuer of a credential or the provider of an OIDC token is trusted. The identity modules add the layer on top: linking those verified external identifiers to internal identity records, and orchestrating the verification workflows that produce those links.
Four modules handle distinct concerns:
Identity Verification (IDV)
IDV defines and executes multi-step verification workflows. A workflow is a directed graph of verification nodes, where each node delegates to a pluggable method driver that performs the actual check.
Method Types
| Type | Driver | What it does |
|---|---|---|
oidc | OidcMethodDefinition | Redirects to an OIDC provider, exchanges the authorization code, extracts identity attributes from the ID token and optional UserInfo endpoint |
wallet | WalletMethodDefinition | Triggers an OID4VP credential presentation request (via DCQL query or presentation definition), verifies the presented credential against trustedIssuers, and extracts attributes |
document | ID verification | Delegates to document verification providers (Onfido, Jumio, ReadID) for passport, ID card, or driving license checks |
biometric | Biometric | Delegates to biometric providers (iProov, Onfido, Jumio) for liveness and face-match verification |
otp | OTP | Sends a one-time password via email, SMS, or authenticator app and validates the user's response |
claim_match | AttributeMatchMethodDefinition | Performs a hash-based lookup against the identity matching store to check whether the subject's identifier (DID, key, email) is already known |
rest_api | REST | Calls a custom external API with configurable authentication (API key, OAuth2 client credentials, mTLS, basic) |
Graph Composition
Verification nodes compose into graphs using four structural node types:
SequenceNode: children execute in order; all must passParallelNode: children execute concurrently with configurable join policy (All,Any,BestEffort) and failure policy (FailFast,WaitForAll,ContinueOnFailure)ChoiceNode: user or system selects one child to execute (policies:UserSelect,HighestAssurance,LowestCost,FirstAvailable)ThresholdNode: at leastminimumSuccessesout of N children must pass
Each MethodNode can bind inputs from context attributes, prior node results, or user-provided form data. Nodes can be conditionally skipped (skipIfAttributePresent).
Assurance and Compliance
Every method definition carries an IdvAssuranceProfile declaring the maximum eIDAS assurance level (LOW, SUBSTANTIAL, HIGH), authentication assurance level (AAL1/AAL2/AAL3), and supported authentication method references (MFA, OTP, face, fingerprint, hardware key, etc.).
Verification results (IdvNodeResult) include the achieved assurance, the evidence type and strength (fair/strong/superior), and optional trust framework metadata. The execution policy enforces a minimum assurance level; if the combined results of the graph don't meet it, the execution fails with an AssuranceError.
Compliance profiles track regulatory frameworks (eIDAS, NIST 800-63A, UK DIATF, DE AML), ETSI LoIP scenarios, territory restrictions, and required consent types (biometric processing, data sharing, cross-border transfer).
Materialization
When an IDV execution completes successfully, materialization rules automatically create downstream records:
| Rule | Effect |
|---|---|
CreateIdentifiersMaterialization | Creates CorrelationIdentifier records (DID, X.509, etc.) linked to the party identity |
CreateIdentityMatchMaterialization | Creates HMAC-hashed IdentityMatch entries in the matching store |
CreateRelationshipMaterialization | Links the subject party to an organization party |
CreateRegistrationMaterialization | Creates a jurisdiction-scoped registration record |
AttachEvidenceMaterialization | Persists verification evidence for audit |
MarkVerifiedMaterialization | Marks the identity as verified for a given role or use case |
This is where the identity framework feeds back into the Party Data Models: the materialization step creates CorrelationIdentifier records with types like IdentifierType.DID or IdentifierType.X509, linking the verified external identifier to an identity in the party store.
How IDV Uses DIDs and Trust
Consider a wallet-based verification flow. The WalletMethodDefinition specifies a set of trustedIssuers (typically DID strings like did:web:gov.example.com) and a DCQL query describing which credentials to request. When the holder presents a verifiable credential:
- The credential's issuer DID is extracted from the proof
- Identifier Resolution resolves the DID to the issuer's public key via the DID document
- The credential signature is verified against that key
- Trust Establishment validates whether the issuer DID is trusted. This may check the
trustedIssuersallow-list, an ETSI trust list, or an OpenID Federation trust chain - If trusted, the verified attributes and identifiers flow into the IDV execution result and downstream materialization
A similar pattern applies for OIDC verification: the OIDC provider's signing keys are resolved via OIDC discovery (an identifier resolution method), the ID token signature is verified, and the provider can be validated via OpenID Federation trust if configured.
Identity Matching
Identity matching maintains privacy-preserving links between external identifiers and internal identity IDs. The core record is:
data class IdentityMatch(
val id: String,
val identifierHash: String, // HMAC-SHA256 of the external identifier
val identifierType: IdentifierType, // KEY, DID, EMAIL, SUBJECT_ID, CLAIM_TUPLE
val internalIdentityId: String,
val tenantId: String,
val metadata: Map<String, String>,
val hashKeyVersion: String?, // Tracks which HMAC key was used
val createdAt: Instant,
val lastUsedAt: Instant?,
)
External identifiers are never stored in plaintext. The ReconciliationCryptoService provides domain-separated hashing with two distinct keys:
- Key A (
hashHolderKey): HMAC-SHA256 for holder key identifiers (e.g., a DID the holder controls) - Key B (
hashExternalIdentifier): HMAC-SHA256 for institution/external identifiers (e.g., a subject ID from an OIDC provider)
For reversible fields (e.g., encrypted identity payloads in reconciliation sessions), Key C provides AES-256-GCM encryption.
Key rotation is supported through dual-read methods (hashHolderKeyWithPrevious, hashExternalIdentifierWithPrevious) that hash with the previous key version during a migration window. The hashKeyVersion field on each match record tracks which key produced the hash.
Relationship to CorrelationIdentifier
There are two complementary identity linking mechanisms:
CorrelationIdentifier(in the Party Data Models) stores the plaintext identifier value (DID string, X.509 subject, etc.) linked to an identity in the party store. This supports direct lookup by value and is used when the identifier is not sensitive.IdentityMatchstores a one-way HMAC hash of the identifier. This is used when the identifier should not be stored in plaintext, e.g., for privacy-sensitive matching scenarios or when the identifier itself is a secret.
Both can be created by IDV materialization. CreateIdentifiersMaterialization creates CorrelationIdentifier records; CreateIdentityMatchMaterialization creates IdentityMatch records.
Identity Resolution
Identity resolution answers: "given this external identifier string, which internal identity ID does it belong to?" This is distinct from Identifier Resolution, which answers: "given this cryptographic identifier, what is the public key?"
| Concept | Input | Output | Module |
|---|---|---|---|
| Identifier Resolution | DID, JWK, X5C, JWKS URL, etc. | Public key (JWK), certificate info | lib-crypto-core-public |
| Identity Resolution | External identifier string + tenant | Internal identity ID | lib-identity-resolution-public |
The IdentityResolver interface is pluggable via multibinding:
interface IdentityResolver {
val resolverId: String // e.g., "identity-matching", "oidc-introspection"
val priority: Int // Higher = tried first
suspend fun supports(tenantId: String, resolverConfig: ResolverConfig?): Boolean
suspend fun resolve(
identifier: String,
tenantId: String,
resolverConfig: ResolverConfig?
): IdkResult<IdentityResolutionResult, IdkError>
}
Multiple resolvers are tried in priority order. A resolver returns resolved = false to pass to the next strategy without failing, which lets you chain strategies like "try hash-based matching first, then fall back to OIDC introspection". An Err result stops the chain (hard failure).
The built-in MatchingIdentityResolverImpl uses the identity matching store: it hashes the incoming identifier with the tenant's HMAC key and looks up the hash. Custom resolvers can implement any strategy (LDAP lookup, external API call, database query) and are contributed per tenant via IdentityResolutionConfig.
Reconciliation
Reconciliation is the policy engine that decides what to do when an external subject arrives. Given context about the incoming request (tenant, entry point, credential types, issuers, holder state), the ReconciliationSelector evaluates a list of ReconciliationSelectorRules and produces a ReconciliationPlan:
sealed interface ReconciliationPlan {
val materialProfileId: String?
val requiredAttributeNames: Set<String>
val selectorRuleVersion: String
}
The concrete plan types:
| Plan | Meaning |
|---|---|
SkipReconciliation | No identity binding needed; pass through |
UseExistingBinding | The subject already has a matching identity; reuse it |
RunIdv | No match found. Starts a full IDV workflow via the specified provider, with a BindingPolicy of REUSE_OR_CREATE, CREATE_NEW, or REUSE_ONLY |
StepUp | Match exists but assurance is insufficient; run additional verification |
FailClosed | Policy requires binding but conditions aren't met; reject with a reason |
Selector Rules
Rules match against the incoming ReconciliationSelectorInput:
data class ReconciliationSelectorRule(
val id: String,
val priority: Int = 0,
val tenants: Set<String>?, // Match specific tenants
val entryPointTypes: Set<String>?, // wallet_oid4vp, federated_oidc, api_call, ...
val triggerTypes: Set<String>?, // onboarding, step_up, revalidation, ...
val credentialTypes: Set<String>?, // mDL, PID, diploma, ...
val issuers: Set<String>?, // DID strings or URL patterns
val knownHolderStates: Set<KnownHolderState>?,
val attributePredicates: List<AttributePredicate>?,
val plan: ReconciliationPlanTemplate,
// ...
)
The issuers field is where reconciliation connects to the DID and trust infrastructure: rules can target specific issuer DIDs (e.g., did:web:gov.nl) or patterns, ensuring that only credentials from trusted issuers trigger specific reconciliation plans.
Reconciliation Sessions
For flows that require an external OAuth2 authorization (e.g., linking an identity via an OIDC provider), the reconciliation module manages ReconciliationSession objects that track the authorization URL, PKCE code verifier, nonce, state, and encrypted identity payload through the redirect flow. Sessions are tenant-scoped and time-limited.
Module Structure
lib/identity/
├── idv/ # Identity Verification
│ ├── public/ # Models, commands, method driver interface, stores
│ ├── oidc/ # OIDC method driver implementation
│ └── wallet/ # Wallet/OID4VP method driver implementation
├── matching/ # Identity Matching
│ ├── public/ # IdentityMatch, IdentifierType, crypto interface, commands
│ └── impl/ # In-memory and Kottage-backed store implementations
├── resolution/ # Identity Resolution
│ ├── public/ # IdentityResolver interface, resolution types, commands
│ └── impl/ # MatchingIdentityResolverImpl
└── reconciliation/ # Reconciliation
├── public/ # Plans, selector rules, session model, commands
└── impl/ # Orchestrator, KMS-backed crypto, session stores
EDK Integration
The identity modules described here provide the core interfaces, models, and pluggable infrastructure. The EDK builds on these with pre-configured verification workflows, managed reconciliation policies, administrative APIs for identity lifecycle management, and production-ready method driver implementations. If you are building a production identity system, the EDK documentation covers the full operational feature set.