Skip to content

GSSO — Consumers and Consumer Contract

Document codeSPEC-GSSO-2026 / Report 03
Version0.1-draft (EN source, governing)
Date2026-10-05
StatusDraft
Companion reports00 RFP · 01 ADR · 02 Requirements · 04 Technical documentation · 05 Roadmap
VersionDateChanges
0.1-draft2026-10-05Consumer inventory (as observed 2026-10-05), target realm/client/role per app, migration paths, API/event/starter contract, onboarding checklist
PatternDescriptionProblem
A — dual auth with token mintingoauth2Login 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 worksLocal signing key and passwords; no SSO logout; Keycloak revocation not effective; two identity sources
B — resource serverThe app validates Keycloak RS256 tokens via issuer/JWKS; SPA does PKCE or JHipster OAuth2Correct, 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 appNo SSO with gStack
D — designed, not builtPlaybook apps: gateway BFF + microservices with OAuth2 via GSSONeeds 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.

Realm interdictii is the current shared realm. “Target” refers to realm gstack unless stated otherwise.

AppKindToday (realm / client / pattern)Target clientsTarget roles (examples)Migration notesPhase
saas_interdictiiSaaSinterdictii / 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_AUDITORReplace 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 realmS4
cancelarieSaaSinterdictii / cancelarie confidential / B (JHipster oauth2, own provisioning in identity/)cancelarie (confidential), cancelarie-apiCANCELARIA_REGISTRATOR, _SEF, _EXECUTOR, _CONTROLOR, _AUDITOR, _ADMINIts employee onboarding calls api/v1/app grant requests (GSSO-FR-051) instead of local admission; local jhipster dev realm replaced by GSSO composeS4
gregistrySaaSinterdictii / gregistry-web public PKCE / B (ADR-002)unchanged ids, moved to gstackGREGISTRY_ADMIN, GREGISTRY_OPERATORPilot of the starter (already pattern B); keeps local-only logout until single logout is wantedS3
gdocsSaaSgdocs (oidc profile) / gdocs-web / Agdocs-web, gdocs-api, gdocs-serviceGDOCS_ADMIN, GDOCS_EDITOR, GDOCS_CITITORSame as interdictii; realm gdocs retiredS4
saas_drumuriSaaSlocal JWT + MPass SAML in app / frontend env wrongly points to glog-web / Cstaff: drumuri-web in gstack; citizens: drumuri-public in cetateanDRUMURI_ADMIN, DRUMURI_OPERATOR, DRUMURI_INSPECTORMPass moves from the app to the cetatean broker; fix frontend env immediately (wrong client id); separate host routes.tech.esempla.systems → redirect URIsS4
saas_crmSaaSdesign only / Dcrm-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-1cCRM_* (from CRM report 02)CAP-GSSO-01 blocker for CRM F0; token exchange to gDocFlow/gTenders (CAP-GSSO-07)S1–S3
saas_gdocflowSaaSdesign / Dgdocflow-gateway, service clientsGDOCFLOW_*Token exchange target and sourceS3
saas_gtendersSaaSdesign / Dgtenders-gateway, service clientsGTENDERS_*Token exchangeS3
saas_gnotifySaaS/PaaSgnotify realm (not created) / OIDC disabled / A; uses glog-ingest client credentialsgnotify-web, gnotify-serviceGNOTIFY_ADMIN, GNOTIFY_OPERATOR; scope notify:send for callersEnable via starter; GSSO uses GNotify through gnotify-service scopeS4
glogPaaSinterdictii / 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:readReplace GlogJwtAuthConverter role→scope mapping with starter + explicit scopes; tenant claim from GSSO mapper; GSSO itself becomes an ingest clientS3
paas_gstoragePaaSrealm gstorage / gstorage-web, gstorage-ingest; API key alternativegstorage-web, gstorage-api, <app>-gstorage service clientsGSTORAGE_ADMIN, GSTORAGE_ARCHIVIST; scopes `storage:readwriteadmin`
paas_gflowPaaSdesign / Dgflow-gateway, gflow-server, per-app service principals (<app>-gflow)GFLOW_ADMIN; candidate groups = consumer rolesUses GET /api/v1/app/roluri/{cod}/utilizatori (GSSO-FR-103); no token exchangeS3
gportal / GDSPaaSno auth (mocks)gportal-web (PKCE)ROLE_USERUses @gstack/gsso-angularS4
platform (docs/RAG)internalinterdictii / gstack-platform PKCE; Python PyJWT + JWKS, needs ROLE_ADMINgstack-platformPLATFORM_ADMINChange role check from ROLE_ADMIN to PLATFORM_ADMINS4
gsso_mob (GovSign)mobilerealm gstack / gstack-govsign-app public PKCE, redirect com.govsign.app://callback; QR approve per sitegovsign-app (public PKCE) in gstack and cetateanROLE_USERRemove 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)sitesrealms ultra / gstack-ultra (unconfirmed)one tenant-<code> realm each, from templateper siteCreated through GSSO from template; QR approve endpoint kept until GSSO-FR-068S4
qa_platformtoolnone (target credentials only)qa-runner service client per environmentQA_RUNNERTest users created by GSSO API in non-prod realmsS5
  1. Register the platform, clients and roles in GSSO, or adopt them from interdictii (GSSO-FR-007).
  2. Create grants that reproduce the current role holders (AtribuireAcces with source ADOPTAT).
  3. Add gsso-spring-boot-starter. Set gsso.issuer-uri, gsso.audience and gsso.role-aliases (for example ROLE_ADMIN: INTERDICTII_ADMIN) for the transition.
  4. Remove the minted-token handler, the local JWT secret, local password login and the /oidc-callback route. For a monolith SPA, switch to @gstack/gsso-angular in BFF or PKCE mode.
  5. 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.
  6. Remove role-aliases and the deprecated ROLE_ADMIN mapping, then delete the old client.

Base path: https://gsso.gstack.esempla.systems/api/v1 (through gsso-gateway).

Conventions:

  • JSON, RFC 7807 errors;
  • Idempotency-Key on every POST;
  • paging with page, size, sort, and X-Total-Count in 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)”
MethodPathPurposeReq
GET/app/platformaown platform with clients and rolesFR-098
PUT/app/platformaself-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, localeFR-101
GET/app/utilizatori/{sub}/rolurieffective roles of the calling platform with scopes and validityFR-101
DELETE/app/utilizatori/{sub}/sesiuniterminate the user’s sessionsFR-102
GET/app/roluri/{cod}/utilizatoriusers holding a role of the calling platform (paged)FR-103
POST/app/atribuirirequest a grant for a user on the calling platform ({sub, rol, orgUnit?, validPana?, motiv}) → SOLICITATAFR-051
GET/app/atribuiri/{id}grant statusFR-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

/health/live, /health/ready, /metrics, /info, /api/v1/openapi.json.

ClaimContentNotes
isshttps://sso.gstack.esempla.systems/realms/gstack
subUUID, stableGSSO-FR-032
audtarget platform audience(s)audience mapper per platform
azpclient id
preferred_username, name, emailprofile
rolesflat array of effective realm roles (platform-prefixed) + ROLE_USERfiltered per audience with scope gsso-roles-filtered
roles_scoped{ "<ROLE>": ["<org_unit>", …] } only for scoped grantsGSSO-FR-055
org_unit, org_unit_pathuser’s org unitGSSO-FR-054
localero / ru / en
idnponly with optional client scope idnpQ-GSSO-13
acr1 / 2 / 3GSSO-FR-070
act{ "sub": "<calling client>" } on exchanged tokensGSSO-ADR-012
tenantrealm 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.

<dependency>
<groupId>systems.esempla.gsso</groupId>
<artifactId>gsso-spring-boot-starter</artifactId>
</dependency>
# application.yml of a consuming service
gsso:
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, locale
RestClient docs = gsso.exchangeClient("gdocflow"); // token exchange (on-behalf-of)
RestClient glog = gsso.serviceClient("glog"); // client credentials

Angular:

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>

All events are CloudEvents in binary mode on Kafka, with source = gsso and subject = <realm>/<sub>.

TopicProducerTypePayload (data)Consumers
gsso.kc-events.v1Keycloak extension gsso-kafkamd.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.v1gssomd.gstack.gsso.access.revoked{sub, realm, roluri[], platforme[], notBefore, motiv}starter in every app
gsso.user-changed.v1gssomd.gstack.gsso.user.changed{sub, realm, campuri[], enabled}apps that cache user data
gsso.grant.v1gssomd.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>.

ObjectConventionExample
Platform codelower-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 realmtenant-<code>tenant-ultra-b2b
Audienceplatform codecrm
ItemDefault
app API rate limit50 req/s per client, burst 100
Self-registrationmax 20 clients and 200 roles per platform
Grant requests by app1 000 / day per platform
Availability offeredKeycloak 99.9 %, console/API 99.5 % (NFR-AVL)
Supportplatform team, working hours; Keycloak outage = P1
Versioningapi/v1 stable; breaking change = v2 with 6 months parallel run
  1. The platform owner is named; the platform code is chosen (§6).
  2. Roles are listed with RO/RU/EN descriptions; sensitive roles are flagged; composites are defined.
  3. Clients are declared: access type, redirect URIs (exact), web origins, back-channel logout URL.
  4. Service clients are declared per calling service, with scopes; token-exchange targets are listed if needed.
  5. The platform is registered in GSSO (console or PUT /api/v1/app/platforma), and its sync status is in sync.
  6. The app uses gsso-spring-boot-starter (and @gstack/gsso-angular). There are no local passwords and no minted tokens.
  7. Audience validation is on. Authorisation uses platform roles only.
  8. The app is subscribed to gsso.access-revoked.v1 (through the starter).
  9. Initial grants are requested and approved; test users exist in non-prod realms.
  10. The CSP allows sso.gstack.esempla.systems in connect-src and form-action.
  11. The E2E login test passes against the GSSO compose stack. The authorization matrix probe (/gtestgen) passes.
  12. The app is added to this report §2 and to the access report.