Authentication and sessions

How users sign in — the built-in OIDC provider, token and session lifetimes, portal users and magic links, and the tenant IP allowlist.

Kelta ships its own OpenID Connect provider (kelta-auth). No external identity server is required; external providers plug in as federated identity sources (SSO).

Ways to sign in#

WhoHow
Internal usersEmail + password on the auth server’s login page, then MFA if enrolled or required.
Internal users via SSOA configured OIDC or SAML provider; the account is provisioned on first login.
Portal usersA magic link sent by email — portal users have no password.
Scripts, CI, agentsA personal access token — no interactive login.
Third-party appsOAuth 2.0 through a connected app.

The OIDC provider#

The web app is a public OAuth client using the authorization-code flow with PKCE against https://auth.example.com/<tenant>/oauth2/authorize and /oauth2/token. The issued JWT carries the user’s tenantId, profileId, groups and user_type; the gateway validates it on every request and re-stamps the identity as internal headers the worker trusts (any client-supplied identity headers are stripped first).

The CLI’s browser login uses the same flow with a loopback redirect (http://127.0.0.1:<port>/<tenant>/auth/callback) and exchanges the short-lived token for a PAT immediately.

Token and session lifetimes#

CredentialLifetime
Web app access token8 hours; refreshed silently with a rotating refresh token (7 days)
CLI login token15 minutes — used only to mint a PAT, then discarded
Connected app tokens1 hour access / 8 hours refresh
Auth-server browser session8 hours
Personal access token1–365 days, chosen at creation (default 90)
Portal magic link15 minutes (login), 7 days (invitation)

A refresh token is single-use; the response carries the next one. There is no separate per-tenant idle-timeout setting: sessions end when the token chain expires, on explicit logout, or when the account is deactivated.

Password policy and lockout#

Password complexity, history, dictionary checks and account lockout are configured per tenant — see MFA and password policy. A user flagged for a forced password change (new accounts, admin reset) must change it before reaching the app.

Portal users (userType: PORTAL) are external people — customers, patients, members. They are invited with POST /api/admin/users/portal-invite (or self-sign-up where the deployment enables it), receive the seeded Portal User profile (API_ACCESS only) and see only records explicitly shared with them. They sign in by requesting a link at /portal/login; tokens are single-use, hashed at rest, and rate-limited per user. Responses are uniform whether or not the email exists.

Logging out#

The app’s logout goes through the auth server’s end-session endpoint. For SAML-federated sessions the platform initiates Single Logout at the identity provider first, then ends its own session, then returns to the app.

Auth server endpoints#

The auth server is its own host (https://auth.example.com), not behind the API gateway. Besides the standard OIDC endpoints (/oauth2/authorize, /oauth2/token, discovery), it serves the sign-in pages and a few JSON endpoints. “Open” means no session is needed; “signed in” needs the auth-server session or a bearer token.

EndpointAccessPurpose
GET /loginopenEmail + password sign-in page.
GET /oauth2/consentsigned inConsent screen a connected app shows on first authorization.
GET/POST /mfa-challenge, POST /mfa-challenge/recoveryopen (mid sign-in)Second factor after the password, or a recovery code.
GET/POST /mfa-setup, POST /mfa-setup/completeopen (mid sign-in)Enrol a TOTP factor when MFA is required.
POST /auth/sms/send, POST /auth/sms/verifysigned inSend and check an SMS one-time code (the SMS factor).
GET/POST /change-passwordopen (mid sign-in)The forced password change after sign-in (new account, admin reset).
POST /auth/password/changesigned inChange your own password; checked against the tenant’s password policy and history.
GET/POST /forgot-password, GET/POST /reset-passwordopenSigned-out password reset pages: request a link, then set a new password.
POST /auth/password/reset-request, POST /auth/password/resetopenThe same reset as JSON for an external frontend; the request always answers the same, account or not.
POST /auth/email/send-verification, GET /auth/email/verifysigned inEmail-ownership verification; the emailed link is valid for 48 hours.
POST /auth/direct-loginopen, off by defaultUsername + password in, tokens out, for headless test runs. Enabled only with kelta.auth.direct-login.enabled=true.
POST/DELETE /auth/sessionopenExchange a federated provider’s token for an auth-server session cookie, and end it.
GET /logout/saml2/initiateopenStarts SAML Single Logout at the identity provider (SSO).
GET /portal/login, POST /portal/login/request, GET /portal/login/verifyopenPortal magic-link pages: request a link, then open it.
POST /portal/api/login/request, POST /portal/api/login/verifyopenThe magic-link flow as JSON for a headless portal; the redirect URI must be on the tenant’s allowlist.
POST /portal/api/signupopen, off by defaultPortal self-sign-up, enabled per tenant; always answers 202.
GET /portal/api/challengeopenA proof-of-work challenge for sign-up and link requests; 404 when the deployment does not require one.

The headless portal’s redirect allowlist is GET/PUT /api/admin/tenant/portal-auth-settings on the API host (MANAGE_USERS): redirectUris, plus the inviteRedirectUri invitation links land on.

Tenant IP allowlist#

A tenant can restrict API access to known networks. Under Setup → Security → Network access (requires MANAGE_TENANTS) enable the allowlist and list CIDR ranges; the fields are ipAllowlistEnabled and ipAllowlistCidrs on the tenants record.

  • Applies to /api/** for every caller whose profile lacks MANAGE_TENANTS — administrators always get through, so a bad range cannot lock everyone out.
  • With KELTA_SECURITY_TRUSTED_PROXIES set, the single resolved client address is checked; forwarded headers count only when they come from a trusted proxy. Without it the check is strict: only the socket peer address is checked and X-Forwarded-For / X-Real-IP are ignored. Behind an ingress that address is the ingress, so the deployment must set KELTA_SECURITY_TRUSTED_PROXIES or non-admin users of a restricted tenant are refused — see Security hardening.
  • Fails open when the configuration is missing or disabled.
  • Inbound webhook endpoints are not subject to it (they authenticate differently — see Triggers).

Changes take effect on every gateway pod within seconds via the config event bus.

Edit this page on GitHub