Identity Reconciliation
Reconciliation answers the fundamental question: "What should the system do when this user presents their wallet credentials?" The answer depends on context. Is the holder key already known? Has the binding expired? Does the credential match any rules? The reconciliation engine is a policy-driven decision system that evaluates these conditions and produces a plan of action -- without any hard-coded business logic.
Every authentication attempt that involves a verifiable credential passes through the reconciliation engine. The engine inspects the current state of the world -- the tenant configuration, the entry point that triggered the flow, the credential presented, and any existing bindings in the database -- and decides what to do next. The output is always one of five possible plans, each representing a distinct operational path. This design makes the system predictable, auditable, and configurable per tenant without touching application code.
Why Reconciliation Exists
A wallet credential -- specifically, a trusted eduID credential from a verified issuer -- proves the holder possesses a cryptographic key and carries identity attributes like the eduPersonPrincipalName. However, the institution's backend systems also need the institution-scoped eduID, which is only available through SURFconext federation. The wallet credential and the institution-scoped identifier live in fundamentally different namespaces with no inherent connection between them.
It is important to understand that the system does not accept arbitrary credentials. The DCQL (Digital Credentials Query Language) configuration defines exactly which credential types and issuers are trusted for reconciliation. Only a valid eduID credential from a trusted issuer triggers the matching and reconciliation process. This trust boundary is the first line of defense.
Reconciliation bridges the gap between the wallet credential and the institutional identity by establishing a trusted link -- once, securely, and with user consent. The first time a user presents their eduID credential from their wallet, the system verifies the credential, extracts the holder key and credential attributes, and then asks the user to authenticate with their institutional account (via an OIDC redirect to SURFconext). This one-time ceremony provides the institution-scoped eduID from SURFconext and creates a cryptographic binding: an HMAC-hashed association between the wallet holder key and the institutional identity, stored in the database with an encrypted attribute envelope containing both the wallet-sourced and OIDC-sourced attributes.
After reconciliation, the link persists in the database, and all future wallet logins resolve instantly. The system looks up the holder key hash, finds the existing binding, decrypts the attribute envelope, and produces an STS token -- all without any user interaction or external calls. This "fast path" is the steady-state experience for returning users, and it completes in milliseconds.
Decision Tree
The following diagram illustrates the reconciliation decision tree. Each branch corresponds to a different evaluation of the input context, and each leaf corresponds to one of the five reconciliation plans.
The decision tree is evaluated top-down. The first condition that matches determines the plan. If no condition matches, the engine falls through to the default behavior, which is typically FailClosed (deny access) unless a catch-all rule has been configured.
Five Reconciliation Plans
The reconciliation engine produces exactly one of five plans. Each plan is a sealed interface variant -- there are no other possibilities. This exhaustive enumeration makes the system's behavior fully predictable and testable.
SkipReconciliation
No action is needed. This plan is used when reconciliation is disabled for the current tenant or not applicable for the entry point type. The authentication completes without identity matching, and the downstream system receives whatever claims were available from the credential alone. This plan is appropriate for entry points where the wallet credential is used purely for attribute presentation rather than identity binding (for example, age verification scenarios where no institutional identity is required).
UseExistingBinding
A valid, non-expired binding already exists for this holder key. The system uses the cached binding directly, decrypts the attribute envelope, and proceeds to STS token generation. This is the "fast path" -- the most common case for returning users after their initial reconciliation. No external calls are made, no user interaction is required, and the entire operation completes from local database state. The binding's last_used_at timestamp is updated to track activity and support inactivity-based expiration policies.
RunIdv (Identity Verification)
The holder key is unknown -- no binding exists in the database for this wallet. The system initiates an identity verification flow: typically, an OIDC redirect to SURFconext where the user authenticates with their institutional credentials. This is the one-time reconciliation ceremony that happens on a user's first wallet login.
The flow creates a reconciliation session to track the OIDC state (PKCE verifier, nonce, state parameter), redirects the user to the identity provider, receives the callback with an authorization code, exchanges it for tokens, extracts the institutional identity claims, and creates both an identity_match record (the HMAC-hashed lookup key) and an identity_link_binding record (the encrypted attribute envelope). After this ceremony, all subsequent logins for the same wallet key follow the UseExistingBinding fast path.
StepUp
A binding exists but does not meet the current assurance requirements. The user needs to re-verify at a higher assurance level. This plan is similar to RunIdv in its mechanics -- it creates a reconciliation session and redirects to an identity provider -- but it is triggered by assurance policy rather than an unknown key.
Step-up scenarios arise when institutional policy changes after the initial binding was created. For example, if an institution raises its minimum assurance level from "low" to "substantial," existing bindings created at the "low" level would trigger StepUp on the next authentication attempt. The user re-authenticates at the higher level, and the binding is updated with the new assurance metadata.
FailClosed
No matching rule applies, and the system is configured to deny access rather than allow unreconciled authentication. This is the safe default when no selector rule matches the input context. The authentication attempt is rejected with an appropriate error, and no binding is created or used.
FailClosed is a deliberate security design choice. In a defense-in-depth configuration, the lowest-priority selector rule should always be a catch-all that produces FailClosed. This ensures that unanticipated combinations of tenant, entry point, credential type, and holder state are denied rather than silently allowed.
How It Works
The reconciliation engine takes an input context and evaluates it against configured selector rules. The process is deterministic: the same input always produces the same plan.
Input context:
- Tenant ID -- which institution or organizational unit initiated the authentication
- Entry point type -- how the authentication was triggered (e.g.,
oid4vpfor wallet presentation,oidcfor traditional login) - Credential types -- which verifiable credential types are present in the VP (Verifiable Presentation)
- Credential issuers -- which issuers signed the credentials
- Known holder state -- the result of the database lookup for the holder key (matched, not found, expired, etc.)
- Extracted attributes -- claim values from the credential that can be used in predicate matching
Evaluation process:
- Filter -- remove all disabled rules from consideration
- Match -- for each remaining rule, check all non-null conditions against the input context; a null condition matches anything, but all non-null conditions must match for the rule to qualify
- Sort -- order qualifying rules by priority (descending), with rule ID as an alphabetical tiebreaker
- Select -- the first qualifying rule wins; its plan template becomes the reconciliation plan
- Default -- if no rule qualifies, the engine returns null, which the caller interprets as
FailClosed
All decisions are data-driven configuration, not code. Different tenants can have different rules. Rules can be updated, reordered, enabled, or disabled without redeployment. The rule evaluation logic itself is fixed and well-tested; the variability lives entirely in the rule definitions.
Further Reading
- Selector Rules -- how to define, configure, and extend the declarative rule engine that drives reconciliation decisions
- Material Profiles -- recipes for constructing identity link bindings, controlling which identifiers are hashed and which attributes are encrypted
- Reconciliation Sessions -- the OIDC-based session lifecycle for identity verification flows, including PKCE, state management, and token exchange