Skip to content

Single sign-on with Entra ID, Okta, or Google

For an internal ERP or line-of-business app, identity usually already lives in a corporate directory — Microsoft Entra ID, Okta, or Google Workspace — with central accounts, groups, MFA, and conditional access. You want Kozou to use that, not a separate consumer login.

It does. Kozou delegates identity: you point it at your existing OpenID Connect (OIDC) provider, and Kozou verifies the tokens that provider issues. The model is the same one used for any provider; for enterprise you simply aim it at your corporate directory.

This guide covers Entra ID first, with the Okta and Google equivalents noted alongside. For the command reference behind it, see kozou dev; for the conceptual picture, see Authentication and authorization.

Three parties, each with one job:

  1. Your identity provider signs the user in (password, MFA, conditional access) and issues a signed OIDC JWT that names who they are and what role they hold.
  2. Kozou verifies that token against the provider’s published keys, then runs the request inside a transaction under SET LOCAL ROLE <role-from-claim>.
  3. PostgreSQL RLS — your own row-level-security policies — decides what that role can read and write.

Kozou is the resource server and enforcement layer: it verifies the token and switches role, but it does not sign users in and it never writes the policies. That keeps identity in the system built to guard it, and access control in the database where it applies to every client.

  • An OIDC identity provider with an app registration — Entra ID, Okta, or Google Workspace.
  • PostgreSQL roles and RLS policies already defined for the roles your app needs (for example app_admin, app_editor, app_author, app_viewer). Kozou switches into these roles; your policies enforce. The login role in database.url must be GRANTed membership in each.
  • A way to deliver the token to requests. Kozou verifies a bearer token but does not run the browser sign-in itself — see Sign-in flow below.

Add an auth block to kozou.config.yaml. Kozou fetches the provider’s keys from its JWKS endpoint (selected by kid, cached, and refreshed on rotation) and checks the issuer and audience. For Entra ID:

auth:
jwt:
jwksUri: https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys
issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
audience: <application-client-id> # the API app's client ID / App ID URI
algorithms: [RS256]
roleClaim: role
allowedRoles: [app_admin, app_editor, app_author, app_viewer]
defaultRole: app_viewer

The endpoints differ by provider, but the shape is identical:

ProviderjwksUriissuer
Microsoft Entra IDhttps://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keyshttps://login.microsoftonline.com/<tenant-id>/v2.0
Oktahttps://<org>.okta.com/oauth2/<server-id>/v1/keyshttps://<org>.okta.com/oauth2/<server-id>
Google Workspacehttps://www.googleapis.com/oauth2/v3/certshttps://accounts.google.com

Use the provider’s v2.0 / OIDC endpoints (for Entra ID, the v2.0 issuer above, not the legacy v1.0 one). Secrets are not involved on Kozou’s side — verification is public-key only.

This is the one step that needs care. Kozou reads a single claim that names the PostgreSQL role to assume — roleClaim (default role) — and runs the request under it. Corporate directories, though, describe membership differently:

  • Entra ID emits app roles as a roles array and group membership as groups (an array of GUIDs).
  • Okta / Google can emit groups or custom claims, also typically as arrays.

So make the provider emit a single scalar claim whose value is the PostgreSQL role name. In Entra ID, define App Roles on the app registration (for example app_editor) and assign users or groups to them, then surface the chosen role as a scalar claim — through an app-role-to-claim or optional-/mapped-claim configuration — named to match roleClaim. Then:

roleClaim: role # the scalar claim naming the DB role
allowedRoles: [app_admin, app_editor, app_author, app_viewer]
defaultRole: app_viewer # role when the token omits the claim
# anonRole: web_anon # role for requests with NO token (else 401)

A token whose role is not in allowedRoles gets 403. A request with no token gets 401 unless you set anonRole, in which case it runs under that anonymous role with empty claims and your RLS decides what it sees.

Today Kozou expects that scalar role claim; consuming a roles array or mapping groups to a role directly is a possible future enhancement. Until then, the App-Role-to-scalar-claim mapping above is the path.

Kozou verifies tokens; it does not run the OIDC authorization-code sign-in or hold a browser session. In an enterprise deployment, that flow belongs to a layer in front of Kozou:

  • an OIDC-aware reverse proxy (for example oauth2-proxy, or Microsoft Entra Application Proxy) that authenticates the user against Entra ID and forwards the resulting bearer token to Kozou’s API, or
  • your own application shell that performs the sign-in and calls the API with the token.

The bundled reference Admin UI (kozou dev) is built for the trusted-token model: under an external identity provider it sends a ready-made token supplied via auth.ui.token (or the KOZOU_ADAPTER_TOKEN environment variable), rather than performing an interactive login of its own. For a full interactive corporate sign-in, put one of the layers above in front.

  • Microsoft Entra ID, Okta, and Google Workspace are OIDC providers with JWKS endpoints and work directly through the auth.jwt configuration above.
  • GitHub is not a general-purpose OIDC login provider (its OAuth apps issue opaque tokens, not verifiable OIDC JWTs for arbitrary audiences). To offer “Sign in with GitHub”, bridge it through an IdP that supports GitHub as a social connector — Microsoft Entra External ID, Auth0, or Keycloak — and point Kozou at that IdP’s JWKS.