Skip to content

Core · Service · Active

platform-auth-service

Shared auth and session service for the workspace, owning cookie sessions, provider configuration, local credentials, OIDC login and trusted identity routes for product backends.

  • TypeScript
  • NestJS 11
  • PostgreSQL
  • JWT/JWKS
  • cookie-parser

Spec sheet

Boundary

Core / Auth

Runtime

NestJS 11 HTTP service

Default port

3100

Persistence

Dedicated auth-postgres via AUTH_DATABASE_URL

Exposure

Public auth surface plus internal trusted routes

Responsibilities

  • Issue and resolve shared HttpOnly sessions across products.
  • Expose provider discovery, CSRF bootstrap and password login.
  • Support OIDC provider start and callback flows.
  • Manage provider configuration and local credentials through guarded admin routes.
  • Provide internal identity, membership and session orchestration routes.
  • Expose JWKS and public key material for token validation.

Interfaces and contract surface

  • GET /health
  • GET /auth/runtime
  • GET /.well-known/jwks.json
  • GET /auth/providers
  • GET /auth/session
  • GET /auth/csrf
  • POST /auth/login/password
  • POST /auth/logout
  • GET /auth/oidc/:providerId/start
  • GET /auth/oidc/:providerId/callback
  • GET /auth/admin/providers
  • GET /auth/admin/providers/:providerId
  • PUT /auth/admin/providers/:providerId
  • GET /auth/admin/capabilities
  • PUT /auth/admin/capabilities/:capabilityCode
  • GET /auth/admin/capability-grants/role
  • PUT /auth/admin/capability-grants/role
  • GET /auth/admin/capability-grants/membership
  • PUT /auth/admin/capability-grants/membership
  • GET /auth/admin/effective-capabilities
  • GET /auth/admin/local-credentials
  • PUT /auth/admin/local-credentials/:subjectId
  • DELETE /auth/admin/local-credentials/:subjectId
  • GET /internal/public-key.pem
  • POST /internal/identities/upsert
  • POST /internal/session/resolve
  • POST /internal/session/issue
  • POST /internal/session/bootstrap
  • DELETE /internal/memberships/by-subject

Consumers

Dependencies and external touchpoints

Notes

  • Auth state is Postgres-only; the previous file-state fallback has been removed.
  • The runtime is split across public, admin and internal route surfaces to keep responsibilities isolated.
  • A bare auth-service startup does not preload local users; product backends may bootstrap identities and credentials via internal routes.

Source references

  • platform-auth-service/README.md
  • docs/core-services-integration.md
  • platform-auth-service/package.json

Come integrarsi davvero ​

Gli esempi sotto servono per smoke test e integrazione backend-to-core. Nel flusso applicativo normale il frontend deve continuare a chiamare il proprio backend/BFF same-origin su /api/auth/*, non auth.cs.lvh.me direttamente.

Persistenza runtime ​

platform-auth-service usa PostgreSQL via AUTH_DATABASE_URL e persiste lo stato auth in tabelle dedicate:

  • platform_auth_identities
  • platform_auth_accounts
  • platform_auth_memberships
  • platform_auth_capabilities
  • platform_auth_capability_grants
  • platform_auth_provider_configs
  • platform_auth_signing_keys
  • platform_auth_oauth_clients
  • platform_auth_oauth_authorization_codes
  • platform_auth_service_accounts
  • platform_auth_api_credentials
  • platform_auth_integration_audit_events

La tabella legacy platform_auth_state con payload JSONB unico non viene piu letta, scritta, migrata o droppata dal runtime. Le membership hanno colonne interrogabili per product_code, subject_id, tenant_id, role e status; solo gli attributi estensibili restano in attributes JSONB.

I token di sessione propagano anche tenantId, role, permissions legacy e capabilities runtime. Le capability sono default-deny: senza grant esplicito la lista capabilities e vuota e i BFF non devono abilitare funzioni CORE o tool MCP. Il ruolo SUPER_ADMIN continua a emettere la permission trasversale platform:* per compatibilita, ma non abilita automaticamente le capability. I token includono anche passwordChangeRequired, cosi i BFF possono forzare il cambio password subito dopo un login con credenziale temporanea.

Le API Credentials sono persistite solo in forma hashata. I PAT human non hanno scope indipendente; i service account hanno un owner identity obbligatorio e usano delega dinamica dell'owner. L'access token integration RS256 è breve e contiene solo riferimenti identificativi: revoca, scadenza, membership e ACL sono verificati nuovamente alla richiesta successiva.

Le capability sono record generici (code, serviceKey, kind, riskLevel, metadata, enabled) e i grant sono assegnati per productCode a uno scope role o membership. Gli override membership possono usare deny, che prevale sugli allow del ruolo.

OAuth per MCP ​

Auth implementa Authorization Code con PKCE S256, Dynamic Client Registration condizionata da PLATFORM_AUTH_OAUTH_DCR_ENABLED, discovery del server e resource-indicator checks. Il metadata /.well-known/oauth-authorization-server pubblica resource_parameter_supported: true e scope abilitati registrati come capability kind: mcp_scope. Lo schema platform_auth_oauth_clients registra i client pubblici e i redirect; platform_auth_oauth_authorization_codes conserva solo hash dei code one-time e lega ciascun code a client, redirect, scope e PKCE challenge.

La superficie pubblica comprende POST /oauth/register, GET/POST /oauth/authorize e POST /oauth/token. Il flusso utente rilascia un bearer breve con audience della risorsa MCP. Le route interne POST /internal/oauth/token/exchange e /internal/oauth/token/resolve sono protette dal token service-to-service: la prima converte sessione o token MCP rispettivamente in audience MCP o audience integrazione; la seconda rivalida audience, scadenza, credential, identità, membership e scope.

Gli scope sono generici per Auth: la capability provider la registra e gli admin assegnano i grant per membership/ruolo del prodotto. Per esempio, il provider Portal usa cs-portal.task-board.read e cs-portal.task-board.write; Auth non contiene una policy speciale per CS Portal. I consumer usano productCode quando una sessione ha membership per più prodotti; ogni token MCP resta limitato a un solo prodotto.

Variabili utili ​

bash
export AUTH_PUBLIC_URL=http://auth.cs.lvh.me:8080
export AUTH_INTERNAL_URL=http://127.0.0.1:3100
export INTERNAL_TOKEN=platform-local-stack-internal-token

1. Bootstrap identity, membership e credenziale locale ​

platform-auth-service non parte con un utente locale pre-caricato. Se stai testando il servizio in isolamento, crea prima identity, membership e password tramite la surface interna.

bash
curl -sS -X POST "$AUTH_INTERNAL_URL/internal/identities/upsert" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "email": "admin@local.test",
    "username": "admin@local.test",
    "password": "ChangeMe123!",
    "enabled": true,
    "membership": {
      "productCode": "funnel-ia-engine",
      "subjectId": "bootstrap-admin",
      "role": "SUPER_ADMIN"
    }
  }'

Nei prodotti reali questo bootstrap viene orchestrato dal backend di prodotto. Un riferimento concreto e funnel-ia-engine/backend/src/auth/auth.service.ts, che sincronizza utenti locali verso /internal/identities/upsert.

Questo e il flusso piu utile per verificare che il servizio emetta davvero la sessione cookie-based condivisa. Le POST pubbliche richiedono double-submit CSRF: prima chiama /auth/csrf, poi invia il token come X-CSRF-Token insieme al cookie jar.

bash
COOKIE_JAR=/tmp/platform-auth.cookies
rm -f "$COOKIE_JAR"

CSRF_TOKEN=$(
  curl -sS -c "$COOKIE_JAR" "$AUTH_PUBLIC_URL/auth/csrf" \
    | node -e "let body=''; process.stdin.on('data', chunk => body += chunk); process.stdin.on('end', () => console.log(JSON.parse(body).csrfToken));"
)

curl -i -sS -X POST "$AUTH_PUBLIC_URL/auth/login/password" \
  -H "Content-Type: application/json" \
  -H "X-CSRF-Token: $CSRF_TOKEN" \
  -b "$COOKIE_JAR" \
  -c "$COOKIE_JAR" \
  -d '{
    "email": "admin@local.test",
    "password": "ChangeMe123!",
    "productCode": "funnel-ia-engine"
  }'

La risposta JSON contiene anche accessToken, ma per validare il comportamento reale conviene poi leggere la sessione usando il cookie appena emesso:

bash
curl -sS "$AUTH_PUBLIC_URL/auth/session" \
  -b "$COOKIE_JAR"

3. Emissione e risoluzione sessione dal backend di prodotto ​

Quando il prodotto ha gia autenticato o provisionato un proprio soggetto, il backend puo chiedere a platform-auth-service di emettere o risolvere una sessione per quello specifico productCode / subjectId.

Emissione token:

bash
curl -sS -X POST "$AUTH_INTERNAL_URL/internal/session/issue" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "productCode": "funnel-ia-engine",
    "subjectId": "bootstrap-admin",
    "authProvider": "local"
  }'

Bootstrap sessione con sostituzione membership di prodotto:

bash
curl -sS -X POST "$AUTH_INTERNAL_URL/internal/session/bootstrap" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -d '{
    "email": "admin@local.test",
    "username": "admin@local.test",
    "authProvider": "local",
    "memberships": [
      {
        "productCode": "cs-portal",
        "subjectId": "portal-admin",
        "role": "PORTAL_ADMIN"
      }
    ]
  }'

Risoluzione del token appena ricevuto:

bash
curl -sS -X POST "$AUTH_INTERNAL_URL/internal/session/resolve" \
  -H "Content-Type: application/json" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -d '{
    "productCode": "funnel-ia-engine"
  }'

Per backend e facade pubbliche, il pattern corretto resta:

  • frontend -> /api/auth/* same-origin
  • backend prodotto -> platform-auth-service
  • header tecnico obbligatorio x-platform-internal-token
  • Authorization: Bearer <session-token> inoltrato solo come contesto utente delegato, mai come sostituto del token interno

4. Admin provider e credenziali locali ​

Le viste operations e i BFF amministrativi usano la surface admin protetta da token interno:

bash
curl -sS "$AUTH_INTERNAL_URL/auth/admin/providers" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"
bash
curl -sS "$AUTH_INTERNAL_URL/auth/admin/local-credentials?productCode=cs-portal" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"

Surface capability:

bash
curl -sS "$AUTH_INTERNAL_URL/auth/admin/capabilities" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"

curl -sS "$AUTH_INTERNAL_URL/auth/admin/effective-capabilities?productCode=cs-chat&subjectId=<SUBJECT_ID>" \
  -H "x-platform-internal-token: $INTERNAL_TOKEN"

Endpoint reference ​

Le route pubbliche usano cookie HttpOnly condivisi e double-submit CSRF sulle POST. Le route admin e internal richiedono sempre x-platform-internal-token: $INTERNAL_TOKEN.

Runtime, chiavi e sessione pubblica ​

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
GET /healthVerifica liveness auth service.Nessun body.curl -sS "$AUTH_INTERNAL_URL/health"{"status":"ok","service":"platform-auth-service"}5xx infrastrutturali. Consumer: status dashboard.
GET /auth/runtimeEspone runtime pubblico necessario ai BFF/UI auth.Nessun body.curl -sS "$AUTH_PUBLIC_URL/auth/runtime"{"cookieName":"cs_session","csrfCookieName":"cs_csrf","sessionTtlSeconds":604800}5xx config non valida. Consumer: BFF/facade auth.
GET /.well-known/jwks.jsonPubblica JWKS per validazione token.Nessun body.curl -sS "$AUTH_PUBLIC_URL/.well-known/jwks.json"{"keys":[{"kty":"RSA","kid":"platform-auth-current","use":"sig","alg":"RS256"}]}5xx signing key non disponibile. Consumer: servizi Core e BFF.
GET /internal/public-key.pemEspone chiave pubblica PEM per consumer interni legacy.Header interno.curl -sS "$AUTH_INTERNAL_URL/internal/public-key.pem" -H "x-platform-internal-token: $INTERNAL_TOKEN"-----BEGIN PUBLIC KEY-----...401/403 token. Consumer: servizi che validano JWT senza JWKS.
GET /auth/providersLista provider login pubblici abilitati.Nessun body.curl -sS "$AUTH_PUBLIC_URL/auth/providers"{"items":[{"id":"local","type":"local","label":"Email e password","enabled":true}]}5xx state store. Consumer: BFF/facade auth.
GET /auth/csrfCrea cookie CSRF e restituisce token da inviare sulle POST pubbliche.Cookie jar opzionale.curl -sS -c "$COOKIE_JAR" "$AUTH_PUBLIC_URL/auth/csrf"{"csrfToken":"csrf_123"}5xx cookie config. Consumer: login/logout facade.
POST /auth/login/passwordLogin locale con cookie sessione condiviso.email, password, productCode; header X-CSRF-Token e cookie CSRF.curl -sS -X POST "$AUTH_PUBLIC_URL/auth/login/password" -H "Content-Type: application/json" -H "X-CSRF-Token: $CSRF_TOKEN" -b "$COOKIE_JAR" -c "$COOKIE_JAR" -d '{"email":"admin@local.test","password":"ChangeMe123!","productCode":"cs-chat"}'{"authenticated":true,"accessToken":"<ACCESS_TOKEN>","user":{"email":"admin@local.test","passwordChangeRequired":false},"memberships":[{"productCode":"cs-chat","subjectId":"chat-admin","role":"SUPER_ADMIN"}],"capabilities":[]}400 body/CSRF, 401 credenziali, 403 provider disabilitato. Consumer: backend/facade prodotto.
GET /auth/sessionRisolve sessione cookie pubblica.Cookie sessione.curl -sS "$AUTH_PUBLIC_URL/auth/session" -b "$COOKIE_JAR"{"authenticated":true,"user":{"email":"admin@local.test","passwordChangeRequired":false},"memberships":[{"productCode":"cs-chat","subjectId":"chat-admin","role":"SUPER_ADMIN"}],"permissions":["platform:*"],"capabilities":["platform:mcp:tool:platform_status_overview:call"]}200 con authenticated:false se assente/scaduta. Consumer: BFF/facade auth.
POST /auth/logoutCancella cookie sessione pubblica.Header/cookie CSRF.curl -sS -X POST "$AUTH_PUBLIC_URL/auth/logout" -H "X-CSRF-Token: $CSRF_TOKEN" -b "$COOKIE_JAR" -c "$COOKIE_JAR"{"authenticated":false}400 CSRF. Consumer: BFF/facade auth.
GET /auth/oidc/:providerId/startAvvia login OIDC con redirect provider.Path provider, query productCode?, redirectUrl?.curl -i "$AUTH_PUBLIC_URL/auth/oidc/google/start?productCode=cs-chat"302 Location: https://accounts.google.com/...404 provider sconosciuto, 403 provider disabilitato. Consumer: facade auth.
GET /auth/oidc/:providerId/callbackCompleta login OIDC e imposta sessione.Query provider OIDC code, state.curl -i "$AUTH_PUBLIC_URL/auth/oidc/google/callback?code=<CODE>&state=<STATE>"302 Location: http://chat.cs.lvh.me:8080/...400 state/code non validi, 401 exchange fallito. Consumer: provider OIDC via browser.
POST /auth/tokenScambia PAT human o client credentials per access token integration breve.grantType, credenziale, audience.curl -sS -X POST "$AUTH_PUBLIC_URL/auth/token" -H "Content-Type: application/json" -d '{"grantType":"personal_access_token","personalAccessToken":"<PAT>","audience":"cs-portal-api"}'{"accessToken":"<ACCESS_TOKEN>","tokenType":"Bearer","expiresIn":300,"audience":"cs-portal-api"}401 credenziale/audience invalida, 503 signing key o database. Consumer: CS Portal facade e bridge.

API Credentials e integrazione ​

Le route sotto sono interne e richiedono sempre il token service-to-service; CS Portal le espone tramite facciata same-origin e non passa ownerIdentityId arbitrario dal browser.

EndpointScopoRequestRisposta rappresentativa
POST /internal/api-credentials/personalCrea PAT human, restituendo il secret una sola volta.identityId, label.credential + secret
GET /internal/api-credentials?identityId=Elenca i riepiloghi PAT dell'identità.Query identityId.{"items":[{"id":"...","status":"active","lastUsedAt":null}]}
POST /internal/api-credentials/:credentialId/rotateRevoca la credenziale corrente e genera un nuovo secret.identityId.credential + secret
DELETE /internal/api-credentials/:credentialId?identityId=Revoca un PAT dell'identità.Query identityId.204 No Content
POST /internal/service-accountsCrea service account e client secret per owner.ownerIdentityId, label da backend trusted.serviceAccount + clientId + secret
GET /internal/service-accounts?ownerIdentityId=Elenca i service account dell'owner con il riepilogo della credenziale corrente, senza secret.Query owner.{"items":[{"id":"...","ownerIdentityId":"...","credential":{"id":"...","kind":"client_secret","status":"active","expiresAt":"...","lastUsedAt":null}}]}
POST /internal/service-accounts/:serviceAccountId/rotateRuota il client secret.ownerIdentityId.credential + clientId + secret
DELETE /internal/service-accounts/:serviceAccountId?ownerIdentityId=Revoca tutte le credenziali del service account.Query owner.204 No Content
POST /internal/integration-token/resolveRisolve access token breve e ricostruisce il principal corrente.Bearer + productCode.principal, user, activeMembership
POST /internal/integration-auditPersiste evento minimizzato e correlabile.Identità, endpoint, esito, requestId.{"recorded":true}

Il token integration non contiene grant ACL o data scope autorevoli. La risoluzione verifica credenziale attiva, audience, signing key, identità, owner/account e membership correnti prima di restituire il contesto.

Admin provider, capability e credenziali ​

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
GET /auth/admin/providersLista provider auth con config redatta.Header interno.curl -sS "$AUTH_INTERNAL_URL/auth/admin/providers" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"id":"local","type":"local","enabled":true},{"id":"google","type":"oidc","enabled":false,"config":{"clientSecret":"***"}}]}401/403 token. Consumer: operations e admin BFF.
GET /auth/admin/providers/:providerIdDettaglio provider auth redatto.Path providerId.curl -sS "$AUTH_INTERNAL_URL/auth/admin/providers/google" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"id":"google","type":"oidc","enabled":false,"config":{"issuer":"https://accounts.google.com","clientSecret":"***"}}404 provider. Consumer: operations.
PUT /auth/admin/providers/:providerIdAggiorna provider auth.JSON provider config.curl -sS -X PUT "$AUTH_INTERNAL_URL/auth/admin/providers/google" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"type":"oidc","enabled":true,"label":"Google","config":{"issuer":"https://accounts.google.com","clientId":"<CLIENT_ID>","clientSecret":"<CLIENT_SECRET>"}}'{"id":"google","type":"oidc","enabled":true,"config":{"clientSecret":"***"}}400 config non valida, 401/403 token. Consumer: operations.
GET /auth/admin/capabilitiesLista capability runtime registrate.Header interno.curl -sS "$AUTH_INTERNAL_URL/auth/admin/capabilities" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"code":"platform:mcp:tool:platform_status_overview:call","serviceKey":"platform-mcp-service","kind":"mcp-tool","riskLevel":"low","enabled":true}]}401/403 token. Consumer: BFF ACL e operations.
PUT /auth/admin/capabilities/:capabilityCodeCrea o aggiorna una capability.Body serviceKey, kind, riskLevel, metadata?, enabled?.curl -sS -X PUT "$AUTH_INTERNAL_URL/auth/admin/capabilities/platform:mcp:tool:platform_status_overview:call" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"serviceKey":"platform-mcp-service","kind":"mcp-tool","riskLevel":"low","enabled":true}'{"code":"platform:mcp:tool:platform_status_overview:call","enabled":true}400 codice/body non valido. Consumer: MCP/catalog sync operations.
GET /auth/admin/capability-grants/role?productCode=&role=Legge grant capability assegnati a un ruolo prodotto.Query opzionali productCode, role.curl -sS "$AUTH_INTERNAL_URL/auth/admin/capability-grants/role?productCode=cs-chat&role=ADMIN" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"productCode":"cs-chat","role":"ADMIN","capabilityCode":"platform:mcp:tool:platform_status_overview:call","effect":"allow"}]}400 query incompleta se richiesta dal service, 401/403 token. Consumer: admin ACL.
PUT /auth/admin/capability-grants/roleSostituisce grant capability di un ruolo.productCode, role, grants.curl -sS -X PUT "$AUTH_INTERNAL_URL/auth/admin/capability-grants/role" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"productCode":"cs-chat","role":"ADMIN","grants":[{"capabilityCode":"platform:mcp:tool:platform_status_overview:call","effect":"allow"}]}'{"productCode":"cs-chat","role":"ADMIN","items":[{"capabilityCode":"platform:mcp:tool:platform_status_overview:call","effect":"allow"}]}400 grant non valido, 404 capability assente. Consumer: admin ACL.
GET /auth/admin/capability-grants/membership?productCode=&subjectId=Legge override capability per membership.Query productCode, subjectId.curl -sS "$AUTH_INTERNAL_URL/auth/admin/capability-grants/membership?productCode=cs-chat&subjectId=chat-admin" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"productCode":"cs-chat","subjectId":"chat-admin","capabilityCode":"platform:mcp:tool:platform_ai_generate_text:call","effect":"deny"}]}400 query incompleta. Consumer: admin ACL.
PUT /auth/admin/capability-grants/membershipSostituisce override capability di una membership.productCode, subjectId, grants.curl -sS -X PUT "$AUTH_INTERNAL_URL/auth/admin/capability-grants/membership" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"productCode":"cs-chat","subjectId":"chat-admin","grants":[{"capabilityCode":"platform:mcp:tool:platform_ai_generate_text:call","effect":"deny"}]}'{"productCode":"cs-chat","subjectId":"chat-admin","items":[{"capabilityCode":"platform:mcp:tool:platform_ai_generate_text:call","effect":"deny"}]}400 grant non valido. Consumer: admin ACL.
GET /auth/admin/effective-capabilities?productCode=&subjectId=Calcola capability effettive dopo role grant e membership override.Query productCode, subjectId.curl -sS "$AUTH_INTERNAL_URL/auth/admin/effective-capabilities?productCode=cs-chat&subjectId=chat-admin" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"productCode":"cs-chat","subjectId":"chat-admin","capabilities":["platform:mcp:tool:platform_status_overview:call"]}404 membership assente. Consumer: BFF e operations.
GET /auth/admin/local-credentials?productCode=Lista credenziali locali per prodotto.Query opzionale productCode.curl -sS "$AUTH_INTERNAL_URL/auth/admin/local-credentials?productCode=cs-portal" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"items":[{"productCode":"cs-portal","subjectId":"portal-admin","email":"admin@local.test","enabled":true,"passwordChangeRequired":false}]}401/403 token. Consumer: operations e admin BFF.
PUT /auth/admin/local-credentials/:subjectId?productCode=Crea o aggiorna password locale per subject.Query productCode, body email?, password, enabled?, passwordChangeRequired?.curl -sS -X PUT "$AUTH_INTERNAL_URL/auth/admin/local-credentials/portal-admin?productCode=cs-portal" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"email":"admin@local.test","password":"ChangeMe123!","enabled":true,"passwordChangeRequired":true}'{"productCode":"cs-portal","subjectId":"portal-admin","email":"admin@local.test","enabled":true,"passwordChangeRequired":true}400 password/query non valida. Consumer: operations bootstrap e reset password admin.
DELETE /auth/admin/local-credentials/:subjectId?productCode=Disabilita/rimuove credenziale locale.Query opzionale productCode.curl -i -sS -X DELETE "$AUTH_INTERNAL_URL/auth/admin/local-credentials/portal-admin?productCode=cs-portal" -H "x-platform-internal-token: $INTERNAL_TOKEN"204 No Content404 credenziale assente. Consumer: operations.

Internal identity e session orchestration ​

EndpointScopoRequestEsempioRisposta rappresentativaErrori e consumer
POST /internal/identities/upsertCrea/aggiorna identity, membership e opzionalmente password locale.email, username?, password?, passwordChangeRequired?, enabled?, membership?.curl -sS -X POST "$AUTH_INTERNAL_URL/internal/identities/upsert" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"email":"admin@local.test","username":"admin@local.test","password":"ChangeMe123!","passwordChangeRequired":true,"enabled":true,"membership":{"productCode":"cs-chat","subjectId":"chat-admin","role":"SUPER_ADMIN"}}'{"identityId":"id_123","subjectId":"chat-admin","email":"admin@local.test","memberships":[{"productCode":"cs-chat","subjectId":"chat-admin","role":"SUPER_ADMIN"}]}400 payload, 409 conflitto subject/email. Consumer: BFF bootstrap utenti.
POST /internal/password/changeCambia password per la sessione corrente e cancella il flag di password temporanea.Header bearer utente, body currentPassword, nextPassword.curl -sS -X POST "$AUTH_INTERNAL_URL/internal/password/change" -H "Content-Type: application/json" -H "Authorization: Bearer <ACCESS_TOKEN>" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"currentPassword":"ChangeMe123!","nextPassword":"NewSecret123!"}'{"authenticated":true,"accessToken":"<ACCESS_TOKEN>","user":{"email":"admin@local.test","passwordChangeRequired":false}}400 password debole/body non valido, 401 token o password corrente non valida. Consumer: BFF prodotto.
POST /internal/session/issueEmette access token per identity/membership esistente; puo includere contesto limitato alla sessione.identityId oppure productCode/subjectId, authProvider?, sessionContext?: {productCode, attributes}. Il prodotto del contesto deve coincidere con quello richiesto.curl -sS -X POST "$AUTH_INTERNAL_URL/internal/session/issue" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"productCode":"cs-portal","subjectId":"portal-admin","authProvider":"local","sessionContext":{"productCode":"cs-portal","attributes":{"sessionClaims":{"salesforceId":"003000000000001"}}}}'{"user":{"id":"<IDENTITY_ID>","memberships":[{"productCode":"cs-portal","subjectId":"portal-admin"}]},"accessToken":"<ACCESS_TOKEN>"}400 contesto non valido o oltre 8 KiB, 403 membership prodotto assente, 404 identity assente. Il contesto firmato e restituito da /internal/session/resolve solo per una membership attiva del prodotto; non modifica le membership persistite. Consumer: BFF prodotto.
POST /internal/session/bootstrapUpsert identity/membership e restituisce sessione in una singola operazione.Identity, authProvider, memberships.curl -sS -X POST "$AUTH_INTERNAL_URL/internal/session/bootstrap" -H "Content-Type: application/json" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"email":"admin@local.test","username":"admin@local.test","authProvider":"local","memberships":[{"productCode":"cs-portal","subjectId":"portal-admin","role":"PORTAL_ADMIN"}]}'{"accessToken":"<ACCESS_TOKEN>","identity":{"email":"admin@local.test","memberships":[{"productCode":"cs-portal","subjectId":"portal-admin","role":"PORTAL_ADMIN"}]}}400 membership non valida. Consumer: BFF migration/bootstrap.
POST /internal/session/resolveRisolve Authorization: Bearer in identity normalizzata e capability di prodotto; restituisce l'eventuale contesto firmato per-sessione solo per una membership attiva dello stesso prodotto.Header bearer, body productCode?.curl -sS -X POST "$AUTH_INTERNAL_URL/internal/session/resolve" -H "Content-Type: application/json" -H "Authorization: Bearer <ACCESS_TOKEN>" -H "x-platform-internal-token: $INTERNAL_TOKEN" -d '{"productCode":"cs-chat"}'{"authenticated":true,"identity":{"email":"admin@local.test","subjectId":"chat-admin","tenantId":null,"role":"SUPER_ADMIN","permissions":["platform:*"],"capabilities":["platform:mcp:tool:platform_status_overview:call"]},"sessionContext":{"productCode":"cs-chat","attributes":{}}}401 bearer assente/scaduto, 403 membership non valida. Consumer: BFF, Core bearer validation, MCP.
DELETE /internal/memberships/by-subject?productCode=&subjectId=Rimuove membership prodotto per subject.Query productCode, subjectId.curl -sS -X DELETE "$AUTH_INTERNAL_URL/internal/memberships/by-subject?productCode=cs-chat&subjectId=chat-admin" -H "x-platform-internal-token: $INTERNAL_TOKEN"{"deleted":true}400 query mancante, 404 membership assente. Consumer: deprovisioning BFF.

I frontend devono continuare a chiamare il proprio BFF same-origin. Solo backend, worker, status/operations e servizi Core usano direttamente queste route.

Timestamp verificato del token ​

Le risoluzioni interne di sessione e token integrazione includono verifiedIssuedAt (epoch millisecondi), derivato esclusivamente da iat dopo verifica crittografica. Un timestamp assente, non intero o futuro oltre la tolleranza e rifiutato. Non viene letto da attributi di membership o claim applicativi arbitrari.

Il CORE espone questa evidenza senza conoscere il dominio HR. I BFF applicano la propria revoca e autorizzazione correnti. Per il rilascio HR di cs-portal, pubblicare prima questa risposta del servizio auth e i contratti, poi il BFF: nessun recupero dal payload non verificato.

Workspace reference: /Users/jeanpaul/projects/cs-repository