Agent Portal
A separate portal for field agents, authenticated against its own Keycloak 'agent' realm. Its first capability is Verifiable Credential issuance; it is built to carry further agent tasks.
The Agent Portal is a second staffed surface on the Registry Platform, alongside the Staff Portal. It exists for a different audience doing a different job: a field agent standing at a counter with a citizen in front of them, rather than a back-office user working through a queue.
It ships with the platform, so every registry manifestation (Farmer Registry, National Social Registry, …) inherits it. It is off by default — agentPortalApi.enabled and agentPortalUi.enabled — because a deployment that has no agents should expose no agent surface.
A generic portal, not a credential tool
Today the portal carries exactly one capability: issuing a Verifiable Credential to an authenticated beneficiary. That is the first task, not the definition of the product.
The landing page is therefore a list of tasks, one card per capability, rather than the issuance form itself. Adding a capability means adding a card; it does not mean restructuring the application. A task the signed-in agent is not permitted to perform is shown disabled with the reason, rather than hidden — an agent who was told they can do something needs to see why they cannot.
Why a separate agent realm
Agents authenticate against a Keycloak realm of their own — agent — not the staff realm.
They are a genuinely different population: typically far more numerous, often contracted rather than employed, and entitled to a much narrower set of actions. Keeping them in their own realm means a staff account grants nothing in the agent portal and an agent account grants nothing in the staff portal, without either side having to be careful about role naming.
Keycloak realm
staff
agent
Confidential client
staff-portal
agent-portal
Login theme
staff-portal
agent-portal
Permission
the full registry catalogue
register:issue_credential
The realm, its confidential client and the client's Secret are created by commons-services' keycloak-init, exactly as the staff realm is. The registry chart adds only the demo agent user. This split matters: the client is confidential and iam-agent-portal-api is its only consumer, so the client and its Secret have to come from the release that configures that API.
How authentication works
The Agent Portal uses the same model as the Staff Portal — deliberately, so there is one authentication design in the platform rather than two.
The UI is a Next.js application whose /api/* routes are a Backend-For-Frontend (BFF). The browser never holds a token:
IAM is the OIDC confidential client. It holds the client secret, performs the code exchange, owns the session, and refreshes tokens silently. The browser sees only httpOnly cookies it cannot read.
Cookie names are prefixed
The agent and staff portals share a parent cookie domain (.<namespace>.<domain>), and browsers match cookies by domain, not origin — so each portal receives the other's cookies. With identical names the second login silently overwrites the first, and each portal's API is then handed the other realm's tokens, which it cannot verify.
The agent portal's cookies are therefore prefixed (agent-X-Access-Token, agent-X-ID-Token, agent-X-Session-Id, agent-X-CSRF-Token) via IAM_AGENT_AUTH_COOKIE_PREFIX. Staff keeps the unprefixed names. Both the IAM service that sets the cookies and the Agent Portal API that reads them must be configured with the same prefix.
Signing out
Sign-out is RP-initiated OIDC logout: the portal calls /api/logout, IAM clears the session cookies and redirects to Keycloak's end_session_endpoint, and Keycloak returns the browser to post_logout_redirect_uri — which IAM takes from the login provider's default_redirect_uri.
global.agentPortalHostname must name the registry's REAL agent UI host on the commons-services install. It defaults to agent-portal.<baseDomain>, but the portal is served by the registry chart at its own host (e.g. fr-agent.<baseDomain>). If the two disagree, login still works and only sign-out breaks — login redirects via the IAM callback, which is registered correctly, while logout sends the browser to a host that serves nothing.
That one value drives three things, which are wrong together:
login_providers.default_redirect_uri— where logout returns tothe Keycloak client's
redirectUris/post.logout.redirect.urisIAM_AGENT_CORS_ALLOW_ORIGINS
Easy to miss, because commons-services has to be told a hostname that only the registry chart really knows.
Permissions
Roles live on the agent-portal Keycloak client and are resolved into permissions by the staff IAM API — /user-access/* exists only there. This requires two things to line up, and returns an empty list (with HTTP 200) if either is missing:
the agent's roles under
resource_access['agent-portal']in the token, andan IAM application registered with mnemonic
agent-portal.
The registry's iam-register Job registers that application, declaring the register:issue_credential and register:verify_credential permissions and the roles that grant them. The two are separate on purpose: checking a card someone presents and creating a new one are different acts with different risk, and a deployment may well want staff who can do the first and not the second.
keycloak-init creates a realm and its client only when absent — it does not reconcile roles onto an existing client, and the Keycloak database outlives a registry reinstall. On an environment created before a role existed, that role is silently skipped even though the chart requests it, and the corresponding card does not appear in the portal. Adding a role to an existing deployment means adding it to the Keycloak client directly.
Current functionality: Verifiable Credential issuance
The agent's job is to establish that the person in front of them is who they claim to be, and then hand them a credential. That is three steps, and the portal presents them on one screen because the citizen is waiting and the authentication expires in minutes.
Step 2 is the point of the whole flow. The agent is authenticated by their own token on every call; the beneficiary is authenticated by eSignet, and that authentication is re-checked at the moment of issue rather than trusted from an earlier screen — the window may well have elapsed while the agent was reading.
Issuing more than one credential type
A registry can define several credential types — vcDefinitions is a list, and each entry carries its own registry view, claim columns, Certify config, card SVG and qr_data_label. No code change is needed to add one.
The agent picks the credential in step 1, before the look-up, and the choice is locked once a record is found. That ordering is not cosmetic: the definition names the view the beneficiary is resolved through, so choosing later would mean looking someone up in one view and issuing them a credential built from another. lookup_beneficiary, start_authentication and issue therefore all carry vc_type.
The selector only appears when more than one type is configured; with a single definition the flow is unchanged.
Current functionality: Verifiable Credential verification
A second card, Verify VC, gated by register:verify_credential.
The agent uploads the printed credential (PDF) or a photo of it, and gets a verdict plus the credential's contents.
The image never leaves the agent's device. A photo of a credential is a photo of someone's identity document; uploading it would put that image in request logs, proxies and every intermediary in between. Only the signed payload the QR already encodes is transmitted.
Showing the contents is the point, not decoration. "Valid" alone says the card is genuine without saying whose it is, so it cannot catch a real credential presented by the wrong person. The fields shown are decoded from the signed payload — exactly what was signed — and only after a SUCCESS.
Every verification is audited with its verdict, so the trail distinguishes a genuine card from a forged one. See Verification.
For the full design — what each step guarantees, what the QR contains, which signature a verifier checks, and how a registry supplies its claims — see Verifiable Credential Issuance and in particular Phase 1 — Paper Credential.
Components
agent-portal-ui
Registry Platform
Next.js app; its /api/* routes are the BFF
agent-portal-api
Registry Platform
Resolves the record, drives the beneficiary's authentication, pushes claims to Certify, renders the PDF, logs the issuance
iam-agent-portal-api
IAM
OIDC confidential client for the agent realm
A variant registry rebuilds agent-portal-api from the platform image with its own extension installed, exactly as it does for staff-api — the service maps the registry's own register-domain model, and running the platform's default model against a variant's database is a schema mismatch.
Extending the portal
A new agent capability needs:
a permission on the
agent-portalclient, declared in the registry'siam-registerpayload;routes on the Agent Portal API, guarded by that permission;
BFF routes in the UI that proxy them, so the browser still holds no token;
a card on the landing page, enabled by that permission.
Nothing about the authentication model changes.
Last updated
Was this helpful?