Skip to content

GSSO — Architecture Decision Records

Document codeSPEC-GSSO-2026 / Report 01
Version0.2 (EN source, governing)
Date2026-10-06
StatusAccepted. 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 reports00 RFP · 02 Requirements · 03 Consumers & contract · 04 Technical documentation · 05 Roadmap
VersionDateChanges
0.1-draft2026-10-05GSSO-ADR-001..014, all Proposed
0.22026-10-06GSSO-ADR-001..014 accepted by the owner (Acceptat); review of individual ADRs may follow through superseding ADRs
0.32026-10-06S1 implementation findings: GSSO-ADR-015 (amends 008) and GSSO-ADR-016 (refines 004), both Proposed

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.
IDTitleStatus
GSSO-ADR-001GSSO is a control plane over Keycloak, not a new identity providerAcceptat
GSSO-ADR-002Hybrid realm model: gstack (staff), cetatean (citizens), tenant-* (isolated)Acceptat
GSSO-ADR-003One Keycloak 26.6.x cluster with its own PostgreSQL; keep the existing instanceAcceptat
GSSO-ADR-004Desired-state reconciliation through the Keycloak Admin REST APIAcceptat
GSSO-ADR-005Platform-namespaced roles and a flat roles claimAcceptat
GSSO-ADR-006Access is granted through AtribuireAcces with a lifecycle and four-eyesAcceptat
GSSO-ADR-007Keycloak events through an event-listener SPI to Kafka; append-only store; GLog forwardingAcceptat
GSSO-ADR-008Users stay in Keycloak; GSSO keeps a read projectionAcceptat
GSSO-ADR-009One integration pattern for apps: gsso-spring-boot-starter and @gstack/gsso-angularAcceptat
GSSO-ADR-010DEV-PLAYBOOK stack: JHipster 9.1.0 microservice gsso + gateway gsso-gateway + gsso-webAcceptat
GSSO-ADR-011Configuration as code: realm baseline, extensions and themes in the repositoryAcceptat
GSSO-ADR-012Token exchange (RFC 8693, Keycloak standard token exchange V2) for on-behalf-of callsAcceptat
GSSO-ADR-013Revocation within 60 s: short tokens, session termination, revocation eventsAcceptat
GSSO-ADR-014EU AI Act triage: GSSO is not an AI systemAcceptat
GSSO-ADR-015User operations are direct, audited Admin API calls (amends GSSO-ADR-008)Proposed
GSSO-ADR-016Reconciler 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.html presents 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 app API for consumers;
    • the admin console.
  • GSSO never handles user passwords and never issues tokens.

Alternatives considered.

  1. 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.
  2. 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.
  3. 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).

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 interdictii is 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.

RealmWhoClientsFederation
masterKeycloak bootstrap and break-glass onlygsso-reconciler (service account)—
gstackAll gStack staff and operators of all institutionsEvery staff-facing SaaS and PaaS client, the GSSO console, service accounts of all servicesAD/LDAP per institution (optional), no MPass
cetateanCitizens and businessesPublic portals and citizen-facing SPAsMPass/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 onlyPer tenant
  • Institutions inside gstack are separated by org units (org_unit claim) 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.

  1. 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.
  2. 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.
  3. 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 gstack becomes critical. Its availability is the availability of every staff app (GSSO-NFR-AVL-*).
  • (−) Existing clients in interdictii must migrate (report 05, phase S4), or interdictii is adopted as gstack (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 container postgres-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 sub for 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 through keycloak-admin-client 26.x with the service account gsso-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 JobSincronizare rows with stare=DRIFT and 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.

  1. keycloak-config-cli / Terraform only: good for static baselines (used in GSSO-ADR-011), but no runtime grants, no approval, and no UI.
  2. Direct database writes to Keycloak: rejected as unsupported and unsafe.
  3. 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_ADMIN and ROLE_USER are shared by every app.
  • Apps read them from realm_access.roles or a custom roles claim.
  • 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 example INTERDICTII_EMITENT, CRM_ADMIN, GLOG_AUDITOR) and carries the attribute gsso.platforma=<cod>.
  • Composite roles group lower ones within the same platform.
  • The default role ROLE_USER is kept for “authenticated staff user”.
  • ROLE_ADMIN is deprecated. It is mapped during migration to <APP>_ADMIN for 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 one roles claim serves every app.
  • The GSSO mapper emits a flat roles array (realm roles of the user, effective after composites).
  • The client scope gsso-roles-filtered can limit the array to the roles of the token’s audience platform plus ROLE_USER, to keep tokens small.
  • The starter maps roles to Spring authorities. <APP>_X becomes the authority <APP>_X; for JHipster compatibility, the app may map <APP>_ADMIN to ROLE_ADMIN locally.

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_APPROVER is also required. Nobody approves their own request.
    • ACTIVA is set only after the reconciler confirms the mapping in Keycloak.
    • validPana is 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.
  • Service accounts get roles through ClientAplicatie configuration, 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 topic gsso.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. gsso consumes the topic idempotently (eventId dedup), writes EvenimentGsso and updates ProiectieUtilizator (last login, failures) and the dashboard aggregates.
  • GSSO’s own events. Catalog changes, grant transitions and sync jobs are written to EvenimentGsso in the same transaction as the change.
  • Store. EvenimentGsso is append-only:
    • the repository has no update or delete method;
    • a database trigger rejects UPDATE and DELETE;
    • each row carries hashPrecedent and hash (SHA-256 chain per realm).
  • Forwarding. A relay forwards every event to GLog POST /api/v1/app/audit-events (client credentials, scope audit: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 (JobSincronizare of type UTILIZATOR). Credential resets and required actions call it synchronously, because they are not desired state.
  • ProiectieUtilizator is 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 sub only.

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 typePattern
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 SPAResource server via the starter; SPA = public client + PKCE via @gstack/gsso-angular (or BFF mode)
Service-to-serviceClient credentials, one confidential client per calling service; token exchange for on-behalf-of (GSSO-ADR-012)
Kafka clientsSASL 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;
    • GssoPrincipal exposing sub, username, orgUnit, locale, idnp?;
    • a client-credentials RestClient/WebClient interceptor and a token-exchange helper;
    • a revocation-event listener (GSSO-ADR-013);
    • a KeycloakHealthIndicator in the readiness group;
    • a Testcontainers helper. It reuses proven code from gregistry (AudienceValidator) and cancelarie (KeycloakHealthIndicator).
  • Angular lib (@gstack/gsso-angular): PKCE login (or BFF session mode), a role guard and *gssoHasRole directive, silent refresh, single logout, and a locale sync from the locale claim.
  • 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.

Status: Acceptat (2026-10-06)

Decision.

  • gsso: JHipster 9.1.0, applicationType: microservice.
    • Package systems.esempla.gsso, authenticationType: oauth2 (GSSO itself, realm gstack), Maven, PostgreSQL, Kafka.
    • languages: ro,ru,en with native ro, skipClient: true.
    • Entities from gsso.jdl.
  • gsso-gateway: JHipster 9.1.0 applicationType: gateway. It is the BFF for gsso-web (client gsso-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 with gdsThemedCss('#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 Keycloak master admin console, kept for the platform team only.

Status: Acceptat (2026-10-06)

Decision.

  • The repository contains the following, applied at deploy time by a one-shot gsso-bootstrap job using keycloak-config-cli, with variable substitution and no secrets:
    • keycloak/realms/gstack.json, cetatean.json and tenant-template.json: the baseline, meaning realm settings, flows, client scopes, mappers, password policy, themes, the event listener, and the gsso-console and gsso-reconciler clients;
    • keycloak/extensions/ (JAR);
    • keycloak/themes/gstack/ (login, account, email);
    • docker-compose.yml and helm/.
  • 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.example lists 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 aud to the target;
    • carry act.sub = the calling client;
    • are limited to the target platform’s roles.
  • 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.

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 flagged sensitive.
  • On revocation, expiry, user disable or “terminate sessions”, GSSO:
    1. removes the mapping or disables the user;
    2. deletes the user’s sessions in Keycloak (logout of the user, which also triggers back-channel logout to the clients that support it);
    3. publishes gsso.access-revoked.v1 on Kafka with sub, the affected platform roles and a not-before time.
  • The starter consumes gsso.access-revoked.v1 and keeps an in-memory deny list keyed on sub + 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).

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-act skill 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 JobSincronizare of type UTILIZATOR.
  • 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 EvenimentGsso in the same request and refreshes ProiectieUtilizator.
  • 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.

  1. Admin client. A thin JSON client on Spring RestClient (KeycloakAdminClient) replaces keycloak-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.
  2. Ownership marker. gsso.managed takes 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 single true value would have let ENFORCE delete the console client and the default ROLE_USER of every tenant realm.
  3. Lightweight access token for gsso-reconciler. Option client.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.
  4. Ordering. The worker claims due jobs with FOR UPDATE SKIP LOCKED and never claims a realm that already has a RUNNING job, so jobs of one realm apply one at a time and in id order, also with several instances (GSSO-FR-079).
  5. Failure classes.
    • Transient: no response, 401, 429, 5xx. Retried with exponential backoff (1 s → 5 min), FAILED after 10 attempts.
    • Permanent: other 4xx, or a baseline-owned object. FAILED at once.

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.