GSSO — Architecture Decision Records
| Document code | SPEC-GSSO-2026 / Report 01 |
| Version | 0.2 (EN source, governing) |
| Date | 2026-10-06 |
| Status | Accepted. GSSO-ADR-001..014 are Acceptat (owner decision, 2026-10-06); the owner may still raise changes, which then need a new superseding ADR |
| Companion reports | 00 RFP · 02 Requirements · 03 Consumers & contract · 04 Technical documentation · 05 Roadmap |
Revision history
Section titled “Revision history”| Version | Date | Changes |
|---|---|---|
| 0.1-draft | 2026-10-05 | GSSO-ADR-001..014, all Proposed |
| 0.2 | 2026-10-06 | GSSO-ADR-001..014 accepted by the owner (Acceptat); review of individual ADRs may follow through superseding ADRs |
| 0.3 | 2026-10-06 | S1 implementation findings: GSSO-ADR-015 (amends 008) and GSSO-ADR-016 (refines 004), both Proposed |
What an ADR is
Section titled “What an ADR is”An ADR records one significant decision:
- context: the situation that called for a decision;
- decision: what was chosen;
- alternatives considered: what was rejected and why;
- consequences: the positive and negative effects the team accepts.
The house style follows gregistry/docs/adr and paas_gflow/docs/reports/01-adr.en.md. Once accepted, an ADR is immutable, and a change needs a new ADR that supersedes it.
Upstream decisions to respect:
saas_crm/docs/reports/DECISIONS.md: D9 (GSSO is the gStack identity platform), D27/D28 (GSSO is an existing PaaS to reuse), D33 (DEV-PLAYBOOK §2 stack for every app).gregistry/docs/adr/ADR-002-auth-oidc-gsso.md: no local passwords, roles in GSSO, SPA uses PKCE, M2M uses client credentials.
| ID | Title | Status |
|---|---|---|
| GSSO-ADR-001 | GSSO is a control plane over Keycloak, not a new identity provider | Acceptat |
| GSSO-ADR-002 | Hybrid realm model: gstack (staff), cetatean (citizens), tenant-* (isolated) | Acceptat |
| GSSO-ADR-003 | One Keycloak 26.6.x cluster with its own PostgreSQL; keep the existing instance | Acceptat |
| GSSO-ADR-004 | Desired-state reconciliation through the Keycloak Admin REST API | Acceptat |
| GSSO-ADR-005 | Platform-namespaced roles and a flat roles claim | Acceptat |
| GSSO-ADR-006 | Access is granted through AtribuireAcces with a lifecycle and four-eyes | Acceptat |
| GSSO-ADR-007 | Keycloak events through an event-listener SPI to Kafka; append-only store; GLog forwarding | Acceptat |
| GSSO-ADR-008 | Users stay in Keycloak; GSSO keeps a read projection | Acceptat |
| GSSO-ADR-009 | One integration pattern for apps: gsso-spring-boot-starter and @gstack/gsso-angular | Acceptat |
| GSSO-ADR-010 | DEV-PLAYBOOK stack: JHipster 9.1.0 microservice gsso + gateway gsso-gateway + gsso-web | Acceptat |
| GSSO-ADR-011 | Configuration as code: realm baseline, extensions and themes in the repository | Acceptat |
| GSSO-ADR-012 | Token exchange (RFC 8693, Keycloak standard token exchange V2) for on-behalf-of calls | Acceptat |
| GSSO-ADR-013 | Revocation within 60 s: short tokens, session termination, revocation events | Acceptat |
| GSSO-ADR-014 | EU AI Act triage: GSSO is not an AI system | Acceptat |
| GSSO-ADR-015 | User operations are direct, audited Admin API calls (amends GSSO-ADR-008) | Proposed |
| GSSO-ADR-016 | Reconciler implementation: thin Admin REST client, ownership markers, lightweight token (refines GSSO-ADR-004) | Proposed |
GSSO-ADR-001 — GSSO is a control plane over Keycloak, not a new identity provider
Section titled “GSSO-ADR-001 — GSSO is a control plane over Keycloak, not a new identity provider”Status: Acceptat (2026-10-06)
Context.
- Keycloak 26.6.3 already serves every gStack app at
sso.gstack.esempla.systems. It is a certified OIDC/SAML provider with MFA, WebAuthn, brokering, LDAP federation, token exchange and an account console. - What is missing is governance:
- a service catalog;
- platform-scoped roles;
- approval of access;
- drift control;
- durable audit;
- one integration standard;
- a console in the gStack design system.
- The design
GSSO.dc.htmlpresents GSSO as “Single Sign-On (Keycloak)”.
Decision.
- GSSO is a control plane around an embedded Keycloak.
- Keycloak owns authentication, credentials, sessions, token issuance, MFA, federation, the login UI (with the GDS theme) and the account self-service.
- GSSO owns:
- the governed model (realms, platforms, clients, platform roles, grants, org units, auth policies);
- its reconciliation into Keycloak;
- the event store;
- reports;
- the
appAPI for consumers; - the admin console.
- GSSO never handles user passwords and never issues tokens.
Alternatives considered.
- Use the stock Keycloak admin console only. Rejected: no catalog, no approval, no drift, no GDS, no audit beyond Keycloak’s own short-lived event store.
- Write a custom identity provider (Spring Authorization Server). Rejected: it would re-implement certified, security-critical features (MFA, WebAuthn, SAML broker, LDAP) at high risk and cost.
- Commercial IAM (Okta, Entra ID). Rejected: sovereignty and hosting constraints for government data, licence cost, and the existing Keycloak investment.
Consequences.
- (+) Security-critical code stays in a mature project, and GSSO stays small.
- (+) Existing apps keep working; the issuer URL does not change for adopted realms.
- (−) Two sources of configuration can diverge, which is mitigated by reconciliation (GSSO-ADR-004).
- (−) GSSO depends on Keycloak Admin API compatibility; upgrades are gated by tests (GSSO-NFR-OPS-004).
GSSO-ADR-002 — Hybrid realm model
Section titled “GSSO-ADR-002 — Hybrid realm model”Status: Acceptat (2026-10-06)
Context.
- Keycloak SSO sessions are per realm. A user logged into realm A is not logged into realm B unless B brokers to A.
- Today realm
interdictiiis the de-facto shared staff realm, while other realm names (gdocs,gnotify,gstorage,gstack,ultra) appear in code with no plan behind them. - The mockup lists one realm per system (
cancelaria,interdictii,portal-cetatean,glog-services). That gives isolation but no shared SSO.
Decision.
| Realm | Who | Clients | Federation |
|---|---|---|---|
master | Keycloak bootstrap and break-glass only | gsso-reconciler (service account) | — |
gstack | All gStack staff and operators of all institutions | Every staff-facing SaaS and PaaS client, the GSSO console, service accounts of all services | AD/LDAP per institution (optional), no MPass |
cetatean | Citizens and businesses | Public portals and citizen-facing SPAs | MPass/eID (SAML broker), first-broker-login links by IDNP |
tenant-<code> | Isolated customers (whitelabel sites ultra-*, bts-*, esempla-*, or an institution that requires its own realm) | Clients of that customer only | Per tenant |
- Institutions inside
gstackare separated by org units (org_unitclaim) and platform roles, not by realms. - Tenant realms are created from a versioned realm template.
- The console realm switcher selects the realm scope of every screen.
Alternatives considered.
- Realm per application (as the mockup). Rejected for staff: no SSO across apps, and roles and users duplicated per realm. It is kept for
tenant-*only. - Single realm for everything, including citizens. Rejected: citizens and staff have different policies (MFA, session length, federation, data minimisation), and staff admin UIs must not be reachable with a citizen account.
- Keycloak Organizations (multi-org inside one realm). Considered for institutions. It is kept as a later option (Q-GSSO-1). Org units through attributes cover the current needs with less coupling to a recent feature.
Consequences.
- (+) One login across all staff apps (goal G-1).
- (+) Citizen data and policy are isolated from staff.
- (−) Realm
gstackbecomes critical. Its availability is the availability of every staff app (GSSO-NFR-AVL-*). - (−) Existing clients in
interdictiimust migrate (report 05, phase S4), orinterdictiiis adopted asgstack(Q-GSSO-1).
GSSO-ADR-003 — One Keycloak 26.6.x cluster with its own PostgreSQL; keep the existing instance
Section titled “GSSO-ADR-003 — One Keycloak 26.6.x cluster with its own PostgreSQL; keep the existing instance”Status: Acceptat (2026-10-06)
Context.
- The live Keycloak is 26.6.3 (container
keycloak, database containerpostgres-keycloak) on the shared demo host behind the edge nginx. - The gstyle design file mentions “Keycloak 24”, but that is outdated.
Decision.
- GSSO pins Keycloak 26.6.x (patch updates allowed) with a dedicated PostgreSQL database, separate from the GSSO database.
- The GSSO repository provides the compose and Helm definitions that reproduce the live instance:
KC_PROXY_HEADERS=xforwarded;- hostname
sso.gstack.esempla.systems; - the extensions JAR and themes mounted;
- health and metrics enabled.
- The live instance is taken over in place, not replaced. Its database is backed up first, and the GSSO extensions and theme are added in a maintenance window approved by the owner.
- Production HA target: 2 Keycloak nodes with the embedded Infinispan cluster (sessions replicated), behind the ingress.
Alternatives considered.
- Fresh Keycloak and data migration: rejected. It would change secrets and user IDs (
sub), and break every app. - Shared PostgreSQL with GSSO: rejected, because the two have different backup, upgrade and blast-radius profiles.
Consequences.
- (+) No change of issuer URL or
subfor existing users. - (−) Takeover needs a careful runbook (report 04 §11) and a rollback point.
GSSO-ADR-004 — Desired-state reconciliation through the Keycloak Admin REST API
Section titled “GSSO-ADR-004 — Desired-state reconciliation through the Keycloak Admin REST API”Status: Acceptat (2026-10-06)
Context.
- GSSO must make Keycloak match its governed model, survive partial failures, and reveal manual changes.
Decision.
- Write path: every catalog or grant change commits, in one transaction, the GSSO entity change plus a
JobSincronizare(transactional outbox). The reconciler worker takes pending jobs (SELECT … FOR UPDATE SKIP LOCKED) and applies them throughkeycloak-admin-client26.x with the service accountgsso-reconciler. - Idempotency: each operation looks up by natural key (realm name, clientId, role name, user id + role) and then creates, updates or no-ops. The Keycloak internal id is stored back as
kcId. - Retry: exponential backoff (1 s → 5 min, max 10 attempts), then
FAILED, which raises an alert through GNotify. - Full reconcile:
- periodically (default every 15 min) and on demand;
- computes a diff per realm between the desired state and Keycloak, for the managed object types only;
- findings become
JobSincronizarerows withstare=DRIFTand a JSON diff.
- Drift policy per realm:
REPORT(default): show only;ENFORCE: auto-correct;IGNORE: for adopted realms during migration.
- Ownership marker: objects created by GSSO carry the attribute
gsso.managed=true. Unmanaged objects are reported as “unmanaged” and are never deleted unless adopted. - Adoption: a read-only import of a realm’s clients, roles and role mappings into the catalog, as
Platforma+ClientAplicatie+RolPlatforma+AtribuireAcces(ACTIVA, sursa=ADOPTAT).
Alternatives considered.
- keycloak-config-cli / Terraform only: good for static baselines (used in GSSO-ADR-011), but no runtime grants, no approval, and no UI.
- Direct database writes to Keycloak: rejected as unsupported and unsafe.
- Synchronous calls from the UI without jobs: rejected, because partial failures leave inconsistent state with no retry.
Consequences.
- (+) Resilient, observable, and replayable.
- (−) The UI must show the sync status of each object (
stareSync). - (−) Eventual consistency: typically under 2 s, bounded by the retry policy.
GSSO-ADR-005 — Platform-namespaced roles and a flat roles claim
Section titled “GSSO-ADR-005 — Platform-namespaced roles and a flat roles claim”Status: Acceptat (2026-10-06)
Context.
- Today realm roles
ROLE_ADMINandROLE_USERare shared by every app. - Apps read them from
realm_access.rolesor a customrolesclaim. - gFlow candidate groups need app-prefixed role names (
CRM_*,GTENDERS_*).
Decision.
- Every role a platform exposes is a realm role named
<PLATFORMA_COD_UPPER>_<ROL>(for exampleINTERDICTII_EMITENT,CRM_ADMIN,GLOG_AUDITOR) and carries the attributegsso.platforma=<cod>. - Composite roles group lower ones within the same platform.
- The default role
ROLE_USERis kept for “authenticated staff user”. ROLE_ADMINis deprecated. It is mapped during migration to<APP>_ADMINfor each app that uses it, then removed (report 05 S4).- Client roles are allowed for service-account scopes (for example
gstorage-service: object:read). Human authorisation uses realm roles only, so that onerolesclaim serves every app. - The GSSO mapper emits a flat
rolesarray (realm roles of the user, effective after composites). - The client scope
gsso-roles-filteredcan limit the array to the roles of the token’s audience platform plusROLE_USER, to keep tokens small. - The starter maps
rolesto Spring authorities.<APP>_Xbecomes the authority<APP>_X; for JHipster compatibility, the app may map<APP>_ADMINtoROLE_ADMINlocally.
Alternatives considered.
- Client roles per app for humans: rejected. Tokens then need a per-client claim structure (
resource_access), and gFlow candidate groups and cross-app queries become harder. - Groups as roles: groups are still used, but only as a way to bundle roles (for example “Operator Cancelaria”). Authorisation is always on roles.
Consequences.
- (+) Clear blast radius per platform (goal G-2).
- (+) One claim for all apps.
- (−) Existing apps must rename their role checks, which is mitigated by the starter’s alias map during migration.
GSSO-ADR-006 — Access is granted through AtribuireAcces with a lifecycle and four-eyes
Section titled “GSSO-ADR-006 — Access is granted through AtribuireAcces with a lifecycle and four-eyes”Status: Acceptat (2026-10-06)
Context.
- Role mappings made directly in Keycloak carry no reason, no approver and no expiry.
- NFRQ56–66 require least privilege and a security audit.
- CAP-GSSO-04 needs fast revocation.
Decision.
- A human’s platform role is assigned only through an
AtribuireAcces:
SOLICITATA ──approve──▶ APROBATA ──reconciled──▶ ACTIVA ──revoke──▶ REVOCATA │ │ └──validPana passed──▶ EXPIRATA └──reject──▶ RESPINSA └──second approval required (sensitive)──▶ stays APROBATA(1/2)- Rules, enforced in the service layer; every transition writes an
EvenimentGsso:- The requester may be the user, their manager, the platform owner, or an app via
api/v1/app. A reason is mandatory. - The approver is the platform owner, or a realm admin. For sensitive roles a second, distinct approver with
GSSO_APPROVERis also required. Nobody approves their own request. ACTIVAis set only after the reconciler confirms the mapping in Keycloak.validPanais optional. Its default comes from the role’s policy (Q-GSSO-5). An hourly scheduler expires due grants. Users get an expiry warning 14 days and 1 day before, through GNotify.- Revocation and expiry remove the mapping and trigger GSSO-ADR-013.
- Admins may grant directly (request and approval in one step) for non-sensitive roles only. This is audited as
ACORDARE_DIRECTA.
- The requester may be the user, their manager, the platform owner, or an app via
- Service accounts get roles through
ClientAplicatieconfiguration, not through grants.
Alternatives considered.
- Keycloak-only role mapping: no governance.
- A BPMN process in gFlow: considered. GSSO is a dependency of gFlow (gFlow authenticates through GSSO), so a circular dependency would arise. The lifecycle is small, and a state machine in the service layer suffices.
Consequences.
- (+) Answers “who has what, why, since when, until when, approved by whom”.
- (−) Adds a step to everyday admin work, which is mitigated by bulk grants and role templates (groups).
GSSO-ADR-007 — Keycloak events through an event-listener SPI to Kafka; append-only store; GLog forwarding
Section titled “GSSO-ADR-007 — Keycloak events through an event-listener SPI to Kafka; append-only store; GLog forwarding”Status: Acceptat (2026-10-06)
Context.
- Keycloak keeps user events and admin events in its own database with a short expiry, and offers no push.
- The dashboard, security events, last-login and the projection need them.
- GLog is the gStack WORM audit store.
Decision.
- Event listener. The GSSO Keycloak extension provides an
EventListenerProvider,gsso-kafka. It publishes every user event (LOGIN, LOGIN_ERROR, LOGOUT, CODE_TO_TOKEN, CLIENT_LOGIN, UPDATE_CREDENTIAL, REGISTER, IDENTITY_PROVIDER_*…) and admin event (with representation) to Kafka topicgsso.kc-events.v1:- CloudEvents binary mode;
- key =
realmId:userId; - asynchronous with a bounded local buffer.
- If Kafka is down, the event is still in the Keycloak event store and the gap is back-filled from the Admin API by the GSSO consumer.
- Consumer.
gssoconsumes the topic idempotently (eventIddedup), writesEvenimentGssoand updatesProiectieUtilizator(last login, failures) and the dashboard aggregates. - GSSO’s own events. Catalog changes, grant transitions and sync jobs are written to
EvenimentGssoin the same transaction as the change. - Store.
EvenimentGssois append-only:- the repository has no update or delete method;
- a database trigger rejects
UPDATEandDELETE; - each row carries
hashPrecedentandhash(SHA-256 chain per realm).
- Forwarding. A relay forwards every event to GLog
POST /api/v1/app/audit-events(client credentials, scopeaudit:write) with retry, recording the GLog receipt id. - Retention. 90 days in GSSO (Q-GSSO-7); GLog holds the long-term trail.
Alternatives considered.
- Polling the Keycloak events API: rejected as the main path because of latency and paging cost. It is kept for back-fill.
- Third-party Kafka listeners: rejected. The CloudEvents envelope and the gStack topic naming are needed.
Consequences.
- (+) Near-real-time dashboard and forensic trail.
- (−) Adds Kafka to the Keycloak runtime dependencies. A Kafka outage does not block logins, because the listener is asynchronous and non-blocking.
GSSO-ADR-008 — Users stay in Keycloak; GSSO keeps a read projection
Section titled “GSSO-ADR-008 — Users stay in Keycloak; GSSO keeps a read projection”Status: Acceptat (2026-10-06)
Context.
- Users and credentials must have one master. The console needs fast search across realms, last-login and MFA status, and grant views by user.
Decision.
- Keycloak is the master of users: identity, attributes, credentials, federation links.
- Console user edits call the Keycloak Admin API through the reconciler (
JobSincronizareof typeUTILIZATOR). Credential resets and required actions call it synchronously, because they are not desired state. ProiectieUtilizatoris refreshed in three ways:- from events;
- by a nightly full sync per realm (paged, 500 per page);
- on demand when a user is opened.
It stores sub, username, name, e-mail,
idnp(encrypted), enabled, MFA types, last login and realm.
- Deleting a user in Keycloak purges the projection and anonymises the actor fields in GSSO’s working tables. Audit rows keep the
subonly.
Consequences.
- (+) No dual master; federation users (LDAP/MPass) are visible.
- (−) The projection may lag by seconds; the console shows the “refreshed at” time.
GSSO-ADR-009 — One integration pattern for apps
Section titled “GSSO-ADR-009 — One integration pattern for apps”Status: Acceptat (2026-10-06)
Context.
- Four patterns exist today (report 00 §2).
- Pattern A, app-minted HS512 JWTs after Keycloak login, keeps a local signing key and local passwords. That defeats SSO logout, revocation and audit.
Decision.
| App type | Pattern |
|---|---|
| JHipster gateway + microservices (playbook) | Gateway = OAuth2 client (BFF, authorization code + PKCE, confidential). Microservices = resource servers. All through gsso-spring-boot-starter |
| Spring Boot monolith with SPA | Resource server via the starter; SPA = public client + PKCE via @gstack/gsso-angular (or BFF mode) |
| Service-to-service | Client credentials, one confidential client per calling service; token exchange for on-behalf-of (GSSO-ADR-012) |
| Kafka clients | SASL OAUTHBEARER with the service’s client credentials |
| Mobile (gsso_mob) | Public client + PKCE + custom scheme redirect |
| Non-Java services (Python RAG, Node) | Validate through JWKS; documented snippet |
- Starter (
systems.esempla.gsso:gsso-spring-boot-starter):- issuer and JWKS validation plus an audience validator;
roles→ authorities, with an optional alias map for migration;GssoPrincipalexposingsub,username,orgUnit,locale,idnp?;- a client-credentials
RestClient/WebClientinterceptor and a token-exchange helper; - a revocation-event listener (GSSO-ADR-013);
- a
KeycloakHealthIndicatorin the readiness group; - a Testcontainers helper.
It reuses proven code from
gregistry(AudienceValidator) andcancelarie(KeycloakHealthIndicator).
- Angular lib (
@gstack/gsso-angular): PKCE login (or BFF session mode), a role guard and*gssoHasRoledirective, silent refresh, single logout, and a locale sync from thelocaleclaim. - Local passwords and app-minted tokens are forbidden for new apps and removed from existing ones during migration (report 03 §2).
Consequences.
- (+) One security review covers all apps; logout and revocation work everywhere.
- (−) Pattern-A apps (interdictii, gdocs, gnotify) need code changes, scheduled in report 05.
GSSO-ADR-010 — DEV-PLAYBOOK stack
Section titled “GSSO-ADR-010 — DEV-PLAYBOOK stack”Status: Acceptat (2026-10-06)
Decision.
gsso: JHipster 9.1.0,applicationType: microservice.- Package
systems.esempla.gsso,authenticationType: oauth2(GSSO itself, realmgstack), Maven, PostgreSQL, Kafka. languages: ro,ru,enwith nativero,skipClient: true.- Entities from
gsso.jdl.
- Package
gsso-gateway: JHipster 9.1.0applicationType: gateway. It is the BFF forgsso-web(clientgsso-console, confidential) and the single API entry; it routes only/api/v1/**and/api/v1/app/**.gsso-web: a separate Angular SPA on@gstack/gds-angular/@gstack/gds-core, themed withgdsThemedCss('#7c3aed')(--gds-accent-gsso), i18n ro/ru/en.keycloak/: the extensions (Maven module, Java 21, Keycloak SPI 26.6.x), themes (FreeMarker + GDS CSS) and realm baselines.- Java, Spring Boot, Angular, PostgreSQL and Kafka versions are exactly those generated by JHipster 9.1.0, pinned at S0. Any deviation requires an ADR.
- Bootstrapping: the console authenticates against the very Keycloak it manages. If GSSO is down, Keycloak and all logins keep working; only governance is paused.
Consequences.
- (+) Same toolchain and conventions as the other gStack apps.
- (−) The console depends on realm
gstack. Break-glass is the Keycloakmasteradmin console, kept for the platform team only.
GSSO-ADR-011 — Configuration as code
Section titled “GSSO-ADR-011 — Configuration as code”Status: Acceptat (2026-10-06)
Decision.
- The repository contains the following, applied at deploy time by a one-shot
gsso-bootstrapjob usingkeycloak-config-cli, with variable substitution and no secrets:keycloak/realms/gstack.json,cetatean.jsonandtenant-template.json: the baseline, meaning realm settings, flows, client scopes, mappers, password policy, themes, the event listener, and thegsso-consoleandgsso-reconcilerclients;keycloak/extensions/(JAR);keycloak/themes/gstack/(login, account, email);docker-compose.ymlandhelm/.
- Runtime objects (platforms, their clients and roles, grants, users) are owned by the GSSO reconciler, not by the baseline files. The two sets are disjoint, so the bootstrap never overwrites runtime objects.
- Secrets (client secrets, the reconciler credentials, SMTP, LDAP bind) come from environment or secret store.
.env.examplelists the names only.
Consequences.
- (+) A Keycloak can be rebuilt from git plus a database backup (goal G-4), and changes are reviewable in merge requests.
GSSO-ADR-012 — Token exchange for on-behalf-of calls
Section titled “GSSO-ADR-012 — Token exchange for on-behalf-of calls”Status: Acceptat (2026-10-06)
Context.
- CAP-GSSO-07: CRM ↔ gDocFlow ↔ gTenders calls must carry the user’s identity so the called app applies the user’s visibility.
Decision.
- Use Keycloak standard token exchange (V2, internal-to-internal), available in 26.x.
- Each target platform has an audience client scope.
- A source client may exchange only for the audiences listed in its
ClientAplicatie.audienteSchimb. GSSO reconciles these lists into the token-exchange permissions or client policies. - Exchanged tokens:
- keep the user
sub; - set
audto the target; - carry
act.sub= the calling client; - are limited to the target platform’s roles.
- keep the user
- Impersonation and external-to-internal exchange are disabled.
Consequences.
- (+) Least-privilege delegation without passing user passwords or long-lived tokens.
- (−) The allowed pairs must be maintained; they are visible in the console and in report 03.
GSSO-ADR-013 — Revocation within 60 s
Section titled “GSSO-ADR-013 — Revocation within 60 s”Status: Acceptat (2026-10-06)
Context.
- CAP-GSSO-03/04 ask that a revoked user or role lose access within 60 s (relaxed to 5 min in some packages).
- Resource servers validate JWTs offline, so a revoked role stays in an unexpired access token.
Decision.
- Access token lifetime: 5 min in
gstack; 2 min for clients flaggedsensitive. - On revocation, expiry, user disable or “terminate sessions”, GSSO:
- removes the mapping or disables the user;
- deletes the user’s sessions in Keycloak (
logoutof the user, which also triggers back-channel logout to the clients that support it); - publishes
gsso.access-revoked.v1on Kafka withsub, the affected platform roles and a not-before time.
- The starter consumes
gsso.access-revoked.v1and keeps an in-memory deny list keyed onsub+ not-before, for the token lifetime. A request with a token issued before the not-before time gets 401. - BFF gateways also drop their server-side session on back-channel logout.
Alternatives considered.
- Token introspection on each request: rejected because of latency and load on Keycloak for all apps. It remains available for very sensitive endpoints.
Consequences.
- (+) Revocation within 60 s for apps using the starter. Others fall back to token expiry (5 min max).
GSSO-ADR-014 — EU AI Act triage
Section titled “GSSO-ADR-014 — EU AI Act triage”Status: Acceptat (2026-10-06)
Decision.
- GSSO is not an AI system under Reg. (EU) 2024/1689 Art. 3(1). It contains no inference, scoring or ranking of persons.
- Risk-based login (adaptive MFA from IP or device signals), if added later, must be rule-based and documented. Any ML-based scoring requires a new triage under the
ai-actskill before design.
GSSO-ADR-015 — User operations are direct, audited Admin API calls
Section titled “GSSO-ADR-015 — User operations are direct, audited Admin API calls”Status: Proposed (2026-10-06). Amends one sentence of GSSO-ADR-008.
Context.
- GSSO-ADR-008 says console user edits go through the reconciler as
JobSincronizareof typeUTILIZATOR. - Users are not desired state: Keycloak is their master.
- Creating a user must return its Keycloak id to the console immediately, and an admin expects a disable or a session kill to take effect now, not after a queue.
Decision.
- User operations call the Keycloak Admin API synchronously through
KeycloakAdminClient: create, edit, enable/disable, required actions, credential removal, session termination, unlock. - Each successful operation appends an
EvenimentGssoin the same request and refreshesProiectieUtilizator. - Keycloak errors map to RFC 7807: 409 conflict, 400 validation, 502 for Keycloak failures.
- The reconciler queue stays for desired state: realms, clients, roles and, from S2, grant role mappings.
Consequences.
- (+) Immediate feedback, and no queue for operations that are not desired state.
- (−) A Keycloak outage fails user operations at once instead of deferring them; the console shows the error.
GSSO-ADR-016 — Reconciler implementation details
Section titled “GSSO-ADR-016 — Reconciler implementation details”Status: Proposed (2026-10-06). Refines GSSO-ADR-004; the decisions there stand.
Context. Building and testing S1 against Keycloak 26.6.3 showed five points that GSSO-ADR-004 left open or got wrong.
Decision.
- Admin client. A thin JSON client on Spring
RestClient(KeycloakAdminClient) replaceskeycloak-admin-client. The library’s RESTEasy stack sits badly next to Spring Boot 4, and GSSO uses a small part of the API. Every Keycloak call still goes through this one class. - Ownership marker.
gsso.managedtakes two values:catalog: created by the reconciler, which may update and delete the object;baseline: owned by the realm baseline files or the tenant template; the reconciler never changes or deletes it, and drift detection skips it. Unmarked objects are unmanaged: reported, never deleted. A singletruevalue would have let ENFORCE delete the console client and the defaultROLE_USERof every tenant realm.
- Lightweight access token for
gsso-reconciler. Optionclient.use.lightweight.access.token.enabled. A full token carries the admin roles of every realm the reconciler created and outgrows HTTP header limits (HTTP 431) after a few dozen realms. Its claims also go stale right after a realm is created (HTTP 403). With the lightweight token (≈780 bytes, constant), Keycloak checks permissions live. A 403 still triggers one token refresh and one retry. - Ordering. The worker claims due jobs with
FOR UPDATE SKIP LOCKEDand never claims a realm that already has aRUNNINGjob, so jobs of one realm apply one at a time and in id order, also with several instances (GSSO-FR-079). - Failure classes.
- Transient: no response, 401, 429, 5xx. Retried with exponential backoff (1 s → 5 min),
FAILEDafter 10 attempts. - Permanent: other 4xx, or a baseline-owned object.
FAILEDat once.
- Transient: no response, 401, 429, 5xx. Retried with exponential backoff (1 s → 5 min),
Consequences.
- (+) Every point is covered by integration tests against a real Keycloak (
ReconcilerIT). - (−) The thin client must be extended by hand when new Admin API calls are needed.