Skip to content
AI Portal Docs
On this page The parts 1/6

The portal never checks a company password itself. Keycloak is the central identity provider and brokers your company’s directory, Entra ID or Okta. SCIMSCIM provisioningCấp tài khoản qua SCIMThe standard your identity provider uses to create, update and remove portal accounts automatically, so people who leave lose access.Open in glossary keeps the people in the portal in step with that directory.

Key rules

  1. Keycloak is the only identity provider the portal talks to. It brokers Entra ID or Okta for single sign-onSingle sign-on (SSO)Đăng nhập một lần (SSO)Signing in once with your company account, such as Entra ID or Okta. The portal never sees your password.Open in glossary.
  2. Applications speak only OIDC. No portal component talks to Entra ID or Okta directly.
  3. Tokens stay on the server. The backend holds the tokens; the browser holds only a session cookie.
  4. SCIM provisions people. Your provider pushes joiners, changes and leavers to the portal, which is the SCIM server.
  5. Every request is checked. The person’s seat is re-read on every request, so a suspension applies on the next request.

The parts

Your deployment holds one organisationOrganisationTổ chứcYour company's space in the portal. Each deployment runs one organisation, inside your own AWS account.Open in glossary, so there is one sign-in path and one company provider behind it.

PartWhat it does
BrowserHolds a session cookie and nothing else
Portal backendHolds the tokens, checks every request and serves SCIM
KeycloakCentral identity provider; renders the sign-in screens and brokers your provider
Entra ID or OktaYour company directory; authenticates people and pushes SCIM changes

Which person fields your provider owns and which the portal owns depends on the directory mode. See Departments › Who owns which field and Departments › Directory mode.

The three identity pages

Request authentication

Two global guards run on every route: first the session guard, then the roles guard. Every route is closed unless it is marked public.

The session guard works through these checks in order:

  1. Session cookie? A browser carries one. A request without a cookie takes the bearer path: the token is verified and its claims are read.
  2. Does the cookie still resolve? If not, the answer is 401. There is no fallback to the bearer path.
  3. Is the token near expiry? It is refreshed before the request continues.
  4. Unsafe method? A request that changes something needs a CSRF header matching the session.
  5. Seat in the organisation? No seat means 401.
  6. Suspended? A person disabled at the provider or suspended in the portal gets 403 access_revoked.
  7. Passes. The roles guard then checks the capabilitiesCapabilityQuyền hạnPermission to do one kind of thing, such as setting budgets or reading the audit log. Roles are the only way to get one.Open in glossary the route needs.
SituationOutcome
No session cookieBearer path: the token is verified and its claims parsed
Cookie that no longer resolves401, with no fallback to the bearer path
Token near expiryRefreshed under a lock
Unsafe methodNeeds a CSRF header matching the session
No seat in the organisation401
Provider disabled or portal suspended403 access_revoked on the next request; sign-in is refused on the same grounds
Token missing a claim401, never a 500
Token signing keys unreachable503

A browser’s context is computed once, at sign-in or refresh. A machine caller is verified on every request. Either way, the seat is read fresh on every request, with no cache.

Why it works this way

Why a suspension answers 403, not 401

The frontend answers a 401 by refreshing the session and signing the person out. A suspended person needs a clear refusal instead, so a suspension returns 403 access_revoked.

Why the seat is re-read on every request

The session carries no department and nothing is cached. Reading the seat on every request means a change of department, role or suspension applies to the very next request, with no wait for a session to expire.

Under the hood

Guard details
  • The bearer path verifies the token against the provider’s JWKS; an unreachable JWKS answers 503.
  • A token near expiry is refreshed under a lock. A request that loses the race re-reads the holder’s result instead of refreshing again.
  • The CSRF header is compared with the session’s value in constant time.
  • The seat is one primary-key read that returns both access fields: status, owned by the provider, and suspended_at, owned by the portal. Both must allow.
  • Token parsing returns an identity without a department. The narrower type forces every full request context to read the seat.
AI Portal Docs
EN VI