GSSO — Consumers and Consumer Contract
| Document code | SPEC-GSSO-2026 / Report 03 |
| Version | 0.1-draft (EN source, governing) |
| Date | 2026-10-05 |
| Status | Draft |
| Companion reports | 00 RFP · 01 ADR · 02 Requirements · 04 Technical documentation · 05 Roadmap |
Revision history
Section titled “Revision history”| Version | Date | Changes |
|---|---|---|
| 0.1-draft | 2026-10-05 | Consumer inventory (as observed 2026-10-05), target realm/client/role per app, migration paths, API/event/starter contract, onboarding checklist |
1. Integration patterns today
Section titled “1. Integration patterns today”| Pattern | Description | Problem |
|---|---|---|
| A — dual auth with token minting | oauth2Login against Keycloak, then OidcAppTokenSuccessHandler mints the app’s own HS512 JWT (copying ROLE_*) and returns it to the SPA via /oidc-callback#id_token=. Local admin/admin login still works | Local signing key and passwords; no SSO logout; Keycloak revocation not effective; two identity sources |
| B — resource server | The app validates Keycloak RS256 tokens via issuer/JWKS; SPA does PKCE or JHipster OAuth2 | Correct, but each app has its own code and role mapping |
| C — local JWT (+ MPass SAML) | Own users and passwords; MPass via SAML directly in the app | No SSO with gStack |
| D — designed, not built | Playbook apps: gateway BFF + microservices with OAuth2 via GSSO | Needs CAP-GSSO-01..07 |
Target for everyone: GSSO-ADR-009. That means a gateway/BFF or resource server through gsso-spring-boot-starter, an SPA through @gstack/gsso-angular, and no local passwords or minted tokens.
2. Consumer inventory and migration
Section titled “2. Consumer inventory and migration”Realm interdictii is the current shared realm. “Target” refers to realm gstack unless stated otherwise.
| App | Kind | Today (realm / client / pattern) | Target clients | Target roles (examples) | Migration notes | Phase |
|---|---|---|---|---|---|---|
| saas_interdictii | SaaS | interdictii / interdictii-web confidential / A (uncommitted manual patch, JHipster 8.11 jwt) | interdictii-web (confidential, BFF) or interdictii-spa (PKCE) + interdictii-api (bearer) | INTERDICTII_ADMIN, INTERDICTII_EMITENT, INTERDICTII_APROBATOR, INTERDICTII_CONSULTANT, INTERDICTII_AUDITOR | Replace OidcAppTokenSuccessHandler with resource server via starter; drop local passwords; map RolUser ADMIN/EMITENT to roles; public consultation API → service clients for banks/ANAF in a tenant- or gstack realm | S4 |
| cancelarie | SaaS | interdictii / cancelarie confidential / B (JHipster oauth2, own provisioning in identity/) | cancelarie (confidential), cancelarie-api | CANCELARIA_REGISTRATOR, _SEF, _EXECUTOR, _CONTROLOR, _AUDITOR, _ADMIN | Its employee onboarding calls api/v1/app grant requests (GSSO-FR-051) instead of local admission; local jhipster dev realm replaced by GSSO compose | S4 |
| gregistry | SaaS | interdictii / gregistry-web public PKCE / B (ADR-002) | unchanged ids, moved to gstack | GREGISTRY_ADMIN, GREGISTRY_OPERATOR | Pilot of the starter (already pattern B); keeps local-only logout until single logout is wanted | S3 |
| gdocs | SaaS | gdocs (oidc profile) / gdocs-web / A | gdocs-web, gdocs-api, gdocs-service | GDOCS_ADMIN, GDOCS_EDITOR, GDOCS_CITITOR | Same as interdictii; realm gdocs retired | S4 |
| saas_drumuri | SaaS | local JWT + MPass SAML in app / frontend env wrongly points to glog-web / C | staff: drumuri-web in gstack; citizens: drumuri-public in cetatean | DRUMURI_ADMIN, DRUMURI_OPERATOR, DRUMURI_INSPECTOR | MPass moves from the app to the cetatean broker; fix frontend env immediately (wrong client id); separate host routes.tech.esempla.systems → redirect URIs | S4 |
| saas_crm | SaaS | design only / D | crm-gateway (confidential), one service client per microservice (crm-core, crm-clienti, crm-comercial, crm-interactiuni, crm-suport, crm-contracte, crm-operational, crm-taskuri, crm-strategie, crm-integrare, crm-cautare, crm-ai), crm-integration-1c | CRM_* (from CRM report 02) | CAP-GSSO-01 blocker for CRM F0; token exchange to gDocFlow/gTenders (CAP-GSSO-07) | S1–S3 |
| saas_gdocflow | SaaS | design / D | gdocflow-gateway, service clients | GDOCFLOW_* | Token exchange target and source | S3 |
| saas_gtenders | SaaS | design / D | gtenders-gateway, service clients | GTENDERS_* | Token exchange | S3 |
| saas_gnotify | SaaS/PaaS | gnotify realm (not created) / OIDC disabled / A; uses glog-ingest client credentials | gnotify-web, gnotify-service | GNOTIFY_ADMIN, GNOTIFY_OPERATOR; scope notify:send for callers | Enable via starter; GSSO uses GNotify through gnotify-service scope | S4 |
| glog | PaaS | interdictii / glog-web PKCE (hardcoded tenant=demo mapper) + glog-ingest service / B (dualauth profile) | glog-web, glog-api, per-caller ingest clients (<app>-glog) | GLOG_ADMIN, GLOG_AUDITOR; client scopes audit:write, audit:read | Replace GlogJwtAuthConverter role→scope mapping with starter + explicit scopes; tenant claim from GSSO mapper; GSSO itself becomes an ingest client | S3 |
| paas_gstorage | PaaS | realm gstorage / gstorage-web, gstorage-ingest; API key alternative | gstorage-web, gstorage-api, <app>-gstorage service clients | GSTORAGE_ADMIN, GSTORAGE_ARCHIVIST; scopes `storage:read | write | admin` |
| paas_gflow | PaaS | design / D | gflow-gateway, gflow-server, per-app service principals (<app>-gflow) | GFLOW_ADMIN; candidate groups = consumer roles | Uses GET /api/v1/app/roluri/{cod}/utilizatori (GSSO-FR-103); no token exchange | S3 |
| gportal / GDS | PaaS | no auth (mocks) | gportal-web (PKCE) | ROLE_USER | Uses @gstack/gsso-angular | S4 |
| platform (docs/RAG) | internal | interdictii / gstack-platform PKCE; Python PyJWT + JWKS, needs ROLE_ADMIN | gstack-platform | PLATFORM_ADMIN | Change role check from ROLE_ADMIN to PLATFORM_ADMIN | S4 |
| gsso_mob (GovSign) | mobile | realm gstack / gstack-govsign-app public PKCE, redirect com.govsign.app://callback; QR approve per site | govsign-app (public PKCE) in gstack and cetatean | ROLE_USER | Remove KEYCLOAK_CLIENT_SECRET from public client config; QR login as Keycloak authenticator (GSSO-FR-068) | S5 |
Whitelabel sites (ultra-b2b, ultra-ecom, bts-licitatii, bts-integrare, esempla-govstec, esempla-sistembancar) | sites | realms ultra / gstack-ultra (unconfirmed) | one tenant-<code> realm each, from template | per site | Created through GSSO from template; QR approve endpoint kept until GSSO-FR-068 | S4 |
| qa_platform | tool | none (target credentials only) | qa-runner service client per environment | QA_RUNNER | Test users created by GSSO API in non-prod realms | S5 |
2.1 Migration recipe for a pattern-A app
Section titled “2.1 Migration recipe for a pattern-A app”- Register the platform, clients and roles in GSSO, or adopt them from
interdictii(GSSO-FR-007). - Create grants that reproduce the current role holders (
AtribuireAcceswith sourceADOPTAT). - Add
gsso-spring-boot-starter. Setgsso.issuer-uri,gsso.audienceandgsso.role-aliases(for exampleROLE_ADMIN: INTERDICTII_ADMIN) for the transition. - Remove the minted-token handler, the local JWT secret, local password login and the
/oidc-callbackroute. For a monolith SPA, switch to@gstack/gsso-angularin BFF or PKCE mode. - Run the app’s E2E tests against the GSSO compose stack, then deploy behind a feature flag that keeps the old client in parallel for one release.
- Remove
role-aliasesand the deprecatedROLE_ADMINmapping, then delete the old client.
3. API contract (summary)
Section titled “3. API contract (summary)”Base path: https://gsso.gstack.esempla.systems/api/v1 (through gsso-gateway).
Conventions:
- JSON, RFC 7807 errors;
Idempotency-Keyon every POST;- paging with
page,size,sort, andX-Total-Countin the response; - RO domain field names.
3.1 app zone — consuming applications (client credentials, scope gsso:app)
Section titled “3.1 app zone — consuming applications (client credentials, scope gsso:app)”| Method | Path | Purpose | Req |
|---|---|---|---|
GET | /app/platforma | own platform with clients and roles | FR-098 |
PUT | /app/platforma | self-register / update own platform, clients, roles (flagged clients only) | FR-099 |
GET | /app/utilizatori?sub=&username=&email=&idnp= | lookup (IDNP only if permitted) | FR-100 |
GET | /app/utilizatori/{sub} | status (enabled, MFA, last login), name, e-mail, org unit, locale | FR-101 |
GET | /app/utilizatori/{sub}/roluri | effective roles of the calling platform with scopes and validity | FR-101 |
DELETE | /app/utilizatori/{sub}/sesiuni | terminate the user’s sessions | FR-102 |
GET | /app/roluri/{cod}/utilizatori | users holding a role of the calling platform (paged) | FR-103 |
POST | /app/atribuiri | request a grant for a user on the calling platform ({sub, rol, orgUnit?, validPana?, motiv}) → SOLICITATA | FR-051 |
GET | /app/atribuiri/{id} | grant status | FR-051 |
3.2 admin zone — console (user session through gsso-gateway)
Section titled “3.2 admin zone — console (user session through gsso-gateway)”These are the resources; full OpenAPI is at /api/v1/openapi.json:
/admin/realms,/admin/realms/{realm}/adoptie,/admin/realms/{realm}/reconciliere/admin/platforme,/admin/platforme/{cod}/clienti,/admin/platforme/{cod}/roluri/admin/clienti/{id}/secret(rotate)/admin/realms/{realm}/utilizatori,…/{sub},…/{sub}/credentiale,…/{sub}/sesiuni,…/{sub}/actiuni(required actions),…/{sub}/deblocare/admin/atribuiri(with/{id}/aprobare,/{id}/respingere,/{id}/revocare)/admin/unitati/admin/realms/{realm}/politica-autentificare/admin/sincronizare/joburi(with/{id}/reincercare,/{id}/acceptare-drift)/admin/evenimente,/admin/evenimente/verificare-lant/admin/tablou(dashboard aggregates)/admin/rapoarte/acces
3.3 Operational endpoints
Section titled “3.3 Operational endpoints”/health/live, /health/ready, /metrics, /info, /api/v1/openapi.json.
3.4 Token contract (realm gstack)
Section titled “3.4 Token contract (realm gstack)”| Claim | Content | Notes |
|---|---|---|
iss | https://sso.gstack.esempla.systems/realms/gstack | |
sub | UUID, stable | GSSO-FR-032 |
aud | target platform audience(s) | audience mapper per platform |
azp | client id | |
preferred_username, name, email | profile | |
roles | flat array of effective realm roles (platform-prefixed) + ROLE_USER | filtered per audience with scope gsso-roles-filtered |
roles_scoped | { "<ROLE>": ["<org_unit>", …] } only for scoped grants | GSSO-FR-055 |
org_unit, org_unit_path | user’s org unit | GSSO-FR-054 |
locale | ro / ru / en | |
idnp | only with optional client scope idnp | Q-GSSO-13 |
acr | 1 / 2 / 3 | GSSO-FR-070 |
act | { "sub": "<calling client>" } on exchanged tokens | GSSO-ADR-012 |
tenant | realm code for tenant-* realms (replaces glog’s hardcoded mapper) |
Access token lifetime: 5 min (2 min for sensitive clients). Signature: RS256, with keys published at /protocol/openid-connect/certs.
4. Starter usage
Section titled “4. Starter usage”<dependency> <groupId>systems.esempla.gsso</groupId> <artifactId>gsso-spring-boot-starter</artifactId></dependency># application.yml of a consuming servicegsso: issuer-uri: https://sso.gstack.esempla.systems/realms/gstack audience: crm role-aliases: # transition only (report 03 §2.1) ROLE_ADMIN: CRM_ADMIN client: # optional: outgoing calls id: crm-comercial secret: ${GSSO_CLIENT_SECRET} revocation: kafka-topic: gsso.access-revoked.v1 # deny-list, GSSO-ADR-013@PreAuthorize("hasAuthority('CRM_ADMIN')")public void delete(...) { ... }
GssoPrincipal p = GssoPrincipal.current(); // sub, username, orgUnit, localeRestClient docs = gsso.exchangeClient("gdocflow"); // token exchange (on-behalf-of)RestClient glog = gsso.serviceClient("glog"); // client credentialsAngular:
provideGsso({ mode: 'bff', loginUrl: '/oauth2/authorization/oidc', logoutUrl: '/api/logout' })// or { mode: 'pkce', issuer, clientId }// route guard: canActivate: [gssoRole('CRM_ADMIN')]// template: <button *gssoHasRole="'CRM_ADMIN'">…</button>5. Event contract
Section titled “5. Event contract”All events are CloudEvents in binary mode on Kafka, with source = gsso and subject = <realm>/<sub>.
| Topic | Producer | Type | Payload (data) | Consumers |
|---|---|---|---|---|
gsso.kc-events.v1 | Keycloak extension gsso-kafka | md.gstack.gsso.kc.user.<TYPE> / md.gstack.gsso.kc.admin.<OPERATION> | Keycloak event fields (type, realm, client, user, ip, error, details / resource type, path, representation without secrets) | gsso only |
gsso.access-revoked.v1 | gsso | md.gstack.gsso.access.revoked | {sub, realm, roluri[], platforme[], notBefore, motiv} | starter in every app |
gsso.user-changed.v1 | gsso | md.gstack.gsso.user.changed | {sub, realm, campuri[], enabled} | apps that cache user data |
gsso.grant.v1 | gsso | md.gstack.gsso.grant.<STARE> | {id, sub, rol, platforma, orgUnit, validDe, validPana, stare} | apps that mirror permissions (e.g. gFlow) |
Key = <realm>:<sub>. Delivery is at-least-once, so consumers must be idempotent on id. Each consumer group has a DLQ topic <topic>.dlq.<group>.
6. Naming conventions
Section titled “6. Naming conventions”| Object | Convention | Example |
|---|---|---|
| Platform code | lower-case, [a-z][a-z0-9-]* | crm, interdictii, glog |
| Role | <PLATFORM_UPPER>_<ROLE> (hyphens become _) | CRM_ADMIN, INTERDICTII_EMITENT |
| SPA client | <platform>-web (PKCE) or <platform>-gateway (BFF) | glog-web, crm-gateway |
| Backend client | <platform>-api (bearer-only) | cancelarie-api |
| Service client | <service> or <caller>-<callee> | crm-comercial, gnotify-glog |
| Client scope (service) | <platform>:<verb> or <object>:<verb> | audit:write, storage:read |
| Tenant realm | tenant-<code> | tenant-ultra-b2b |
| Audience | platform code | crm |
7. Quotas and service level
Section titled “7. Quotas and service level”| Item | Default |
|---|---|
app API rate limit | 50 req/s per client, burst 100 |
| Self-registration | max 20 clients and 200 roles per platform |
| Grant requests by app | 1 000 / day per platform |
| Availability offered | Keycloak 99.9 %, console/API 99.5 % (NFR-AVL) |
| Support | platform team, working hours; Keycloak outage = P1 |
| Versioning | api/v1 stable; breaking change = v2 with 6 months parallel run |
8. Onboarding checklist for a new app
Section titled “8. Onboarding checklist for a new app”- The platform owner is named; the platform code is chosen (§6).
- Roles are listed with RO/RU/EN descriptions; sensitive roles are flagged; composites are defined.
- Clients are declared: access type, redirect URIs (exact), web origins, back-channel logout URL.
- Service clients are declared per calling service, with scopes; token-exchange targets are listed if needed.
- The platform is registered in GSSO (console or
PUT /api/v1/app/platforma), and its sync status is in sync. - The app uses
gsso-spring-boot-starter(and@gstack/gsso-angular). There are no local passwords and no minted tokens. - Audience validation is on. Authorisation uses platform roles only.
- The app is subscribed to
gsso.access-revoked.v1(through the starter). - Initial grants are requested and approved; test users exist in non-prod realms.
- The CSP allows
sso.gstack.esempla.systemsinconnect-srcandform-action. - The E2E login test passes against the GSSO compose stack. The authorization matrix probe (
/gtestgen) passes. - The app is added to this report §2 and to the access report.