DigiD (NL Citizen eID)¶
Sign in Dutch citizens with DigiD — the national citizen eID. The connector ships with HelixIAM and is configurable from the console; going live needs your own accepted DigiD connection.
What it is¶
DigiD is the Netherlands' citizen electronic identity. With HelixIAM the DigiD connector is built in — there is no add-on to install. You add it like any other identity provider, and the realm can authenticate citizens with their DigiD identity, with the resulting user provisioned just-in-time and mapped into your realm.
You bring the DigiD agreement
The connector is included, but DigiD is a regulated scheme: to connect to the DigiD pre-production (Preprod) or production environment you need your own accepted DigiD connection — directly or through a recognised broker — along with the environment's trust material (certificates, metadata, and a minimum level of assurance). Helix supplies the protocol connector and facilitated defaults; you supply the agreement and trust material.
DigiD is one of HelixIAM's flagship differentiators — a built-in EU eID connector that most identity products leave you to build yourself.
DigiD is an identity provider with protocol: "digid". It builds on the SAML 2.0 SP profile but adds the eID-specific material: an SP decryption key (the assertion — and thus the BSN — is encrypted to the SP), an SP signing key (the AuthnRequest must be XML-signed), and a minimum level of assurance.
Binding support. Helix supports the classic SAML2 HTTP-Artifact binding — back-channel ArtifactResolve over mutual-TLS SOAP — as well as HTTP-POST, selectable per connection so you can match the profile your environment requires. With the artifact binding, the back-channel ArtifactResponse must carry a valid signature by the IdP's metadata signing key: an unsigned response is refused, and so is any response when the connection has no IdP signing certificate (idpSigningCertificate).
In the console¶
- Open Federation & eIDs → Identity providers and click Add identity provider.
- Choose DigiD. This applies facilitated defaults for the scheme, so most of the connection is pre-shaped and you supply only your environment-specific details.
- Supply your environment-specific trust material and service details.
- Select the binding — HTTP-Artifact (with the ArtifactResolve back-channel over mutual TLS) or HTTP-POST — to match your connection profile.
- Configure attribute mappers for any other attributes you consume. The BSN itself never goes through a mapper: see How the BSN is handled.
- Save; the provider loads at runtime and appears as a DigiD sign-in option.
Operational note — production accreditation
The DigiD connector is built in and configurable now. Connecting to production, however, is a formal scheme process: admission and accreditation are arranged through Logius, and include a mandatory annual security assessment. Plan that accreditation track separately from the technical configuration, which you can complete immediately.
Over the API¶
DigiD is created like any identity provider, with protocol: "digid". The examples below assume you have set $HELIX_URL / $REALM and logged in — see Authenticating to the API.
Create a DigiD connector¶
The config map carries the SAML-SP endpoints and the eID trust material. Set responseBinding to artifact for the classic DigiD "Koppelvlak SAML" back-channel resolve, or post for the modern front-channel profile.
# Refresh CSRF after any GET, then POST (see the auth guide for the helper)
curl -s -b cookies.txt -c cookies.txt "$HELIX_URL/admin/realms/$REALM/identity-providers" >/dev/null
CSRF=$(awk '($6=="__Host-XSRF-TOKEN"||$6=="XSRF-TOKEN")&&$7~/^r1\./{t=$7} END{print t}' cookies.txt)
curl -s -b cookies.txt \
-H "Content-Type: application/json" \
-H "X-XSRF-TOKEN: $CSRF" \
-d '{
"alias": "digid",
"protocol": "digid",
"displayName": "DigiD",
"enabled": true,
"config": {
"ssoUrl": "https://digid.example/saml/idp/request_authentication",
"idpEntityId": "https://digid.example/saml/idp/metadata",
"spEntityId": "https://auth.example.com/realms/acme/digid",
"assertionConsumerServiceUrl": "https://auth.example.com/realms/acme/broker/digid/callback",
"idpSigningCertificate": "-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----",
"spDecryptionPrivateKey": "-----BEGIN PRIVATE KEY-----\n…\n-----END PRIVATE KEY-----",
"spSigningPrivateKey": "-----BEGIN PRIVATE KEY-----\n…\n-----END PRIVATE KEY-----",
"spSigningCertificate": "-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----",
"minimumLoa": "loa3",
"subjectAttribute": "bsn",
"responseBinding": "artifact",
"artifactResolutionServiceUrl": "https://digid.example/saml/idp/resolve_artifact"
}
}' \
"$HELIX_URL/admin/realms/$REALM/identity-providers"
{
"realmId": "acme",
"alias": "digid",
"protocol": "digid",
"displayName": "DigiD",
"enabled": true,
"config": {
"ssoUrl": "https://digid.example/saml/idp/request_authentication",
"idpEntityId": "https://digid.example/saml/idp/metadata",
"spEntityId": "https://auth.example.com/realms/acme/digid",
"assertionConsumerServiceUrl": "https://auth.example.com/realms/acme/broker/digid/callback",
"minimumLoa": "loa3",
"subjectAttribute": "bsn",
"responseBinding": "artifact",
"artifactResolutionServiceUrl": "https://digid.example/saml/idp/resolve_artifact",
"spDecryptionPrivateKeySet": true,
"spSigningPrivateKeySet": true
}
}
The SP private keys are write-only: the response reports spDecryptionPrivateKeySet / spSigningPrivateKeySet instead of the keys — see secrets are write-only.
Config keys
SAML-SP endpoints (ssoUrl, idpEntityId, spEntityId, assertionConsumerServiceUrl, idpSigningCertificate) plus the eID material: spDecryptionPrivateKey, spSigningPrivateKey, spSigningCertificate, minimumLoa (e.g. loa3; see Levels of assurance), subjectAttribute (the attribute that carries the BSN, when it is not the NameID), authnRequestBinding (post/redirect), responseBinding (post/artifact), and artifactResolutionServiceUrl when the response binding is artifact. A wizard connection may instead carry metadataUrl / metadataXml.
How the BSN is handled¶
Under the Wabb and article 87 of the UAVG, only an organisation with a legal basis may process a citizen service number (BSN). Helix keeps it away from everything that does not need it:
- Never the username. A DigiD sign-in provisions an account with a non-identifying username,
digid-plus 12 random hex characters (templateHELIX_BSN_USERNAME_TEMPLATE, default{alias}-{random}; it has no placeholder that could carry the BSN).preferred_usernametherefore never contains a BSN;subis the internal user id. - Linked by a keyed hash. The federated link is keyed on
HMAC-SHA256(realm pepper, BSN), not the BSN; a returning citizen is found by that hash. The pepper is the realm's secret (stored encrypted withDB_ENCRYPTION), so DigiD sign-in needsDB_ENCRYPTION. - Stored encrypted, per realm. The number itself is kept in its own table per realm and user, encrypted with
DB_ENCRYPTION(AES-GCM), outside the profile attributes. A BSN collected in one realm is never shown in, or released by, another realm the same user belongs to. - Only from a trusted source. A BSN is recorded only from the subject of a DigiD connection or of an eIDAS
connection with a Dutch PersonIdentifier, or from a provider a realm administrator explicitly marked as a BSN source
(Identity providers → (provider) → Settings → Trust this provider as a source of BSNs, config key
bsnAuthoritative; needsrealm-admin, audited asIDP_BSN_AUTHORITATIVE_CHANGED, never carried in by a realm import). While a provider is marked, repointing it (issuer, endpoints, JWKS, metadata, certificates, client credentials, trusted lists, protocol, enabled) also needsrealm-admin, through the API, the console or an import. From any other provider a BSN-shaped value is removed but never stored. A recorded BSN is never overwritten by a different one: that sign-in is refused andBSN_CONFLICTis audited. - No BSN-shaped usernames. A username in the DigiD sector form or an 8/9-digit number that passes the 11-proef is refused by the admin API, self-registration, SCIM and imports.
- Recognised as the DigiD sector form
s00000000:<bsn>(NameID or attribute, on any provider), a bare number that passes the 11-proef on a DigiD connection or typed as a BSN, an eIDAS PersonIdentifier from NL, or absnclaim/attribute of an OIDC or SAML broker. Attributes that contain it are dropped before mappers run. A DigiD Machtigen representative's BSN (actingSubject) is stored as its hash. - Masked in the console. Users show
*****1234(bsnMaskedin the admin API). Revealing the full number needs thereveal-bsnpermission and is audited (BSN_REVEALED). Log lines and audit events carry the hash, never the number. The realm export carries no BSN; GDPR anonymisation removes it.
Releasing the BSN to an application¶
By default no application receives the BSN. A realm administrator can opt one client in, recording the legal basis:
in the console under Applications → (client) → Advanced → Citizen service number (BSN), or SAML clients →
NameID & attributes. The application then receives the bsn member of the userinfo response — never inside an
ID or access token, which are persisted and forwarded to resource servers; call userinfo with the access token — or the SAML
attribute urn:nl-eid-gdi:1.0:id:BSN (FriendlyName bsn). No protocol mapper or profile attribute can emit a bsn
claim. Every change is audited (BSN_RELEASE_GRANTED / BSN_RELEASE_REVOKED, with the legal basis).
curl -s -b cookies.txt -c cookies.txt "$HELIX_URL/admin/realms/$REALM/bsn-release" >/dev/null
CSRF=$(awk '($6=="__Host-XSRF-TOKEN"||$6=="XSRF-TOKEN")&&$7~/^r1\./{t=$7} END{print t}' cookies.txt)
# Release the BSN to the OIDC client "wmo-portal" (realm-admin only)
curl -s -b cookies.txt -X PUT \
-H "Content-Type: application/json" \
-H "X-XSRF-TOKEN: $CSRF" \
-d '{"protocol": "oidc", "clientId": "wmo-portal", "enabled": true,
"legalBasis": "Wmo 2015 art. 2.3.2 (maatwerkvoorziening)"}' \
"$HELIX_URL/admin/realms/$REALM/bsn-release"
{ "protocol": "oidc", "clientId": "wmo-portal", "enabled": true,
"legalBasis": "Wmo 2015 art. 2.3.2 (maatwerkvoorziening)", "grantedBy": "…", "grantedAt": 1792267251799 }
For a SAML service provider use "protocol": "saml" and its entity id as clientId; "enabled": false stops the
release. The grant is bound to the client's internal id: it needs an existing client (404 otherwise), is deleted with
the client, and a client created later under the same id inherits nothing. A session an administrator impersonates
never receives the BSN (audited as BSN_SUPPRESSED_IMPERSONATION). Releasing needs a legalBasis of at least 10 characters (400 with fieldErrors otherwise) and
realm-admin (403 otherwise).
Method & path (under /admin/realms/{realm}) |
Purpose |
|---|---|
GET /bsn-release |
Clients and SAML providers that receive the BSN, with their legal basis (view-clients) |
PUT /bsn-release |
Turn the release on or off for one client (realm-admin, audited) |
POST /users/{userId}/bsn/reveal |
The user's full BSN (reveal-bsn, audited, never cached) |
Upgrading accounts from before this release
Accounts DigiD created earlier had the BSN as username and as link key. At the first start Helix moves them, once
(new username, link re-keyed on the hash, BSN removed from profile attributes and the audit log in the database;
audited as BSN_MIGRATION). The BSN is recorded only for links of a trusted source; a BSN-shaped username without
one is only replaced (BSN_MIGRATION_SKIPPED). Citizens keep their accounts. Relying parties that read the BSN from
preferred_username must be opted in to the bsn claim instead. The BSN remains in older backups and external log
stores until they expire.
Levels of assurance¶
minimumLoa is both requested in the AuthnRequest (RequestedAuthnContext Comparison="minimum") and enforced on the assertion. DigiD levels map to the SAML 2.0 context classes defined in Logius' Koppelvlakspecificatie DigiD SAML Authenticatie v3.5:
| DigiD level | minimumLoa keywords |
Requested and ranked class (urn:oasis:names:tc:SAML:2.0:ac:classes:…) |
|---|---|---|
| Basis | loa1, basis |
PasswordProtectedTransport |
| Midden | loa2, midden |
MobileTwoFactorContract |
| Substantieel | loa3, substantieel |
Smartcard |
| Hoog | loa4, hoog |
SmartcardPKI |
The four full class URNs are accepted as values too. A broker that asserts the NL eID-stelsel URNs urn:nl-eid-gdi:1.0:LoA:Low / Midden / Substantial / High is accepted at the same rungs, and such a URN may still be stored as the minimum: it is compared as stored, but the AuthnRequest asks for the context class of the same level, because DigiD does not know the nl-eid-gdi URNs in a request.
Unknown minimums are refused
A minimumLoa the server does not know is refused when you save the connection (400 with fieldErrors.minimumLoa) and when a realm import contains it; a stored one is skipped when it loads. A blank minimum means no minimum. Before rc.8 an unknown minimum silently accepted every level — see the upgrade guide.
A broker that requires the nl-eid-gdi URN in the AuthnRequest is not supported as a DigiD connection. The generic SAML broker sends no RequestedAuthnContext and enforces no eID minimum. Routing services may assert other URIs (for example eIDAS …/LoA/NotNotified/*), which are not ranked and so never meet a set minimum.
Full endpoint set¶
Method & path (under /admin/realms/{realm}) |
Purpose |
|---|---|
GET /identity-providers · POST /identity-providers |
List / create providers |
GET · PUT · DELETE /identity-providers/{alias} |
Read / update / delete a provider |
See Identity providers for the shared flow, and the API reference for every field.
See also¶
- Identity providers — broker model, JIT provisioning, SLO
- eHerkenning (NL business) · eIDAS (EU cross-border)
- SAML broker — the underlying SAML 2.0 profile
- Users & credentials · Authentication flows