Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.44.0.
Authenticate citizens with eSignet over Base Registry Engine (BReg)
If you run a population registry on Base Registry Engine (BReg) and an eSignet deployment, the eSignet BReg provider lets citizens sign in to public services against your registry. eSignet keeps the login, the consent screen, and the signed OpenID Connect (OIDC) tokens. Your registry answers one governed lookup and releases only the fields each citizen approves. On the registry side that takes one selector profile, one access profile, one runtime entry, and one client at your issuer, for the provider version your deployment runs.
How a sign-in reaches your registry
Section titled “How a sign-in reaches your registry”Relying parties talk only to eSignet, which runs the OIDC endpoints and the consent screen and signs the ID Token and UserInfo response. The provider, a Go module compiled into the eSignet host, verifies the citizen’s challenge, looks the citizen up in your registry, and hands eSignet the claims to sign.
sequenceDiagram
participant C as Citizen
participant E as eSignet
participant P as BReg provider
participant I as OAuth issuer
participant B as BReg
C->>E: Identifier and challenge response
E->>P: Verify challenge
P->>I: private_key_jwt client_credentials
I-->>P: Short-lived access token
P->>B: Lookup account-check fields
B-->>P: uin, status
C->>E: Approve requested claims
E->>P: Fetch approved claims
P->>B: Lookup approved fields
B-->>P: Domain data
P-->>E: Claims and pairwise subject
E-->>C: Signed ID Token and UserInfo
The citizen’s tokens never reach your registry.
The provider authenticates as a private_key_jwt client to the OAuth issuer your registry already
trusts.
It asks for a fixed resource and scope set and caches each access token, for five minutes by default.
BReg trusts one issuer only, so the provider’s client must be registered there.
An ID Token is addressed to the relying party, not to your registry, and BReg refuses it.
On every call BReg verifies the token’s signature, issuer, type, lifetime, and audience, and that
its azp or client_id is in allowedClients.
What a token must carry lists each requirement.
The token must then satisfy the profile the provider names in accessProfile, because naming a
profile grants nothing.
Three claims decide that:
- The principal claim, here
registry_principal, recorded in the audit journal as the caller. - The required scope, here
registry:esignet:lookup, which you grant to the provider’s client alone. - The row claim, here
registry_eligible_status, which lists the statuses the provider may resolve.
A token that misses any of these gets a refusal, never a narrower answer. The provider adds its own safeguards:
- It verifies the challenge before it calls the issuer or your registry.
- Its first lookup asks only for
account_check_fields, usually the identifier and a status. - eSignet asks for consent on every sign-in. The second lookup asks only for approved claims the provider maps and provisions, and an empty set makes no call.
- The OIDC
subclaim is a keyed hash of the relying party, the client, and the identifier. Each relying party sees a different subject, which does not reveal the identifier.
Grant the provider one lookup
Section titled “Grant the provider one lookup”The examples use a person Record type on the population route with a uin identifier field.
Declare the lookup in the project
Section titled “Declare the lookup in the project”Add a selector profile to the Record type and an access profile that grants only that lookup:
recordTypes: - id: person route: population # fields: uin, status, given-name, family-name, birthdate selectorProfiles: - id: by-uin fields: [uin]
accessProfiles: - id: esignet-source principalClaim: registry_principal requiredScopes: ["registry:esignet:lookup"] permissions: records: - recordType: person operations: [lookup] readableFields: [uin, status, given-name, family-name, birthdate] lookups: - selector: by-uin valueOrigin: request rowBoundaries: - field: status claim: registry_eligible_status operator: inlookups ties the grant to by-uin, and valueOrigin: request lets the provider send the
identifier it verified.
The profile grants no get, list, or write operation, because the provider never needs one.
The row boundary resolves only Records whose status the row claim lists, so an inactive Record
answers like a missing one.
Read the compiled grant:
bregctl explain access populationThe esignet-source block reads as follows; the rest of the output is omitted:
profile esignet-source actor kind not declared requester clients any principal claim registry_principal task grant none operations ["lookup"] required scopes (all) ["registry:esignet:lookup"] allowed purposes (any) unrestricted readable fields ["birthdate","family-name","given-name","status","uin"] writable fields none filterable fields none sortable fields none row restrictions (all) [{"claim":"registry_eligible_status","field":"status","operator":"in"}] lookups [{"claimMapping":{},"selector":"by-uin","valueOrigin":"request"}]If lookups names a selector the Record type does not declare, bregctl refuses the project with
breg.access-profile.lookup-selector-unknown.
Without the row boundary it warns breg.access.profile-unrestricted-rows, because every Record on
the route could then resolve.
Admit the provider’s client
Section titled “Admit the provider’s client”In the runtime file, list the provider’s client in allowedClients.
Its audience and principal claim must match what the issuer will put in the token:
authentication: oidc: issuer: https://issuer.example.com audience: urn:breg:population allowedClients: [esignet-source, registry-admin] authorityClaims: principal: registry_principalOther keys stay as your deployment sets them. Check the project and the runtime file together:
bregctl check population --runtime-config population/runtime.yamlIt prints Authoring check passed; registry revision sha256:<digest>. first and exits 0.
Then deploy the changed project as
Check, test, package describes.
Issue the provider’s token
Section titled “Issue the provider’s token”At your OAuth issuer, register the esignet-source client to authenticate with private_key_jwt.
Give it an ES256 or RS256 key whose private half the provider reads from a file.
Grant it the registry:esignet:lookup scope and nothing that writes.
Configure its access tokens to carry:
audequal tourn:breg:population, the value the provider requests as itsresource.registry_principal, naming the provider as the caller.registry_eligible_status, an array of the statuses that may sign in, such as["active"].
Decode one token issued to this client and confirm each claim before you go on. A token without the row claim fails every lookup as if no citizen existed.
Point the provider at the registry
Section titled “Point the provider at the registry”Select the provider with MOSIP_ESIGNET_AUTHN_PROVIDER=breg and name its one YAML file in
REGISTRY_ESIGNET_CONFIG_FILE.
These keys connect it to the grant:
subject_id_type: uinpsut_secret_file: /run/secrets/registry-psutbreg: base_url: https://registry.example.com route: population selector: by-uin selector_field: uin access_profile: esignet-source provisioned_fields: [uin, status, givenName, familyName, birthdate] account_check_fields: [uin, status]token_client: token_endpoint: https://issuer.example.com/oauth2/token assertion_audience: https://issuer.example.com/oauth2/token client_id: esignet-source private_key_file: /run/secrets/registry-client.jwk key_id: esignet-source-2026-09 resource: urn:breg:population scopes: ["registry:esignet:lookup"]claim_map: sub: $psut given_name: givenName family_name: familyName birthdate: birthdateroute, selector, and access_profile name what the project declares; client_id,
resource, and scopes match the issuer and the runtime file.
selector_field, provisioned_fields, and account_check_fields take API names, which default to
the camelCase form of each field ID, so given-name becomes givenName.
account_check_fields must include selector_field.
Every claim a citizen may release needs a claim_map entry, and sub must be $psut.
Protect and rotate the two secret files on your own schedule.
Sign in with a synthetic identifier your registry holds. The consent screen follows the challenge, or eSignet reports one of the codes below.
The provider’s demo block accepts one-time codes from a static file instead of proving that the
citizen holds the identifier.
Leave it disabled in production, where the eSignet host must have a real challenge verifier and
refuses to start without one.
What each refusal means
Section titled “What each refusal means”BReg answers each refusal with a problem document,
and the provider reads its code.
The provider collapses those codes, so a relying party never learns whether an identifier exists:
| BReg answers | When | Provider reports |
|---|---|---|
401 authentication.refused | The token’s signature, issuer, type, lifetime, or audience fails, its client is not in allowedClients, or it lacks the principal claim. | registry_auth_unavailable, after dropping its cached token |
404 resource.not_found | The token lacks the profile’s scope, principal claim, or row claim; the profile does not exist or grant lookup; or $select names a field it cannot read. | registry_auth_subject_denied |
404 lookup.unresolved | No single Record matches inside the row boundary, or the profile has no lookups entry for the selector. | registry_auth_subject_denied |
400 request.invalid | The values keys in the request body are not the selector’s API names. | registry_auth_unavailable |
A profile that grants lookup with no lookups entry passes bregctl check, yet every lookup
answers lookup.unresolved.
Because both 404 codes look alike, a grant mistake reads as an unknown citizen.
When every sign-in is denied, compare a decoded token with the bregctl explain access output
before you suspect the data.
The audit journal records each refused lookup with its principal and selected profile, but not
which check failed.
Timeouts, redirects, and oversized responses also report registry_auth_unavailable.
The provider’s other codes never involve your registry.
registry_auth_challenge_failed reports a failed challenge.
registry_auth_context_invalid and registry_auth_context_expired report a five-minute sign-in
context that does not match or has lapsed.
- Query a registry from Python and Node to read the same registry from your own application.
- Control access and reads for every profile key used here.
- Data minimization and purpose limitation by design for the principles this integration applies.
Cookies on Registry Stack
We use optional cookies to understand how you use the main Registry Stack website and this documentation. If you contact us, we may link this activity to your enquiry. You can change your choice at any time. Read the privacy policy.
Your browser asks sites not to track you, so optional tracking stays off.
We could not use or save that choice. Optional tracking remains off.