Skip to main content

Authentication model

Tokens​

Keycloak issues OAuth 2.0 access tokens, signed JWTs, from the realm startupos. The frontend asks for them. Services only validate them.

TokenIssued byCarried howLifetime
Access tokenKeycloak, through the frontend's /api/auth/token and /api/auth/refreshAuthorization: Bearer header30 minutes
Refresh tokenKeycloakHttpOnly cookie startupos_session, path /api/authFollows the realm's refresh settings
Stream tokenLMS servicetoken query parameter on video and recording stream URLs6 hours
Service tokenKeycloak, client credentialsBearer header on service-to-service callsKeycloak default
LiveKit webhook JWTLiveKit serverAuthorization header with no Bearer prefixPer webhook

How people sign in today​

The pilot uses a demonstration sign-in. A person picks one of a fixed set of demo personas on the sign-in page and enters a password. The frontend's server route exchanges them with Keycloak for a token. Per-person accounts with their own credentials are not yet in place. The persona allow-list in frontend/src/lib/dev-personas.ts is what limits the route to those accounts.

How services decide who is calling​

  1. The OIDC layer verifies the token and builds the security identity.
  2. CallerIdentity and RoleEnforcementFilter read the principal and roles from that identity.
  3. Header values are consulted only if HeaderIdentityPolicy is enabled, which is true only in the dev and test profiles or when explicitly switched on. Production Deployments do not set it, and a startup guard refuses to start a production service in which it is on.

Roles and tenants​

A Spoke Coordinator's token must carry a tenant claim that matches the tenant of the data requested. Platform-wide roles (hub_admin, govt_viewer) are exempt from the tenant match. The exemption is about scope, not permission: a CM Office user still needs the role a given endpoint requires.

Service-to-service​

Services call each other with a client-credentials token. Roles are not borrowed: a service token carries none of the platform roles and cannot pass a @RequiresRole gate on a user's behalf.

Sources: frontend/src/app/api/auth/**, libs/common-models/src/main/java/com/rtih/startupos/common/security/, .kiro/steering/auth-rollout.md, docs/runbooks/keycloak-fix-final-report.md.