DocsCore concepts

Authentication & sessions

Four independent authentication mechanisms, all converging on the same permission-checking identity.

KHUB supports three independent authentication mechanisms, plus enterprise SSO, all converging on the same AuthenticatedUser principal used by every permission check.

Logging in via the web UI issues two httpOnly, SameSite=Lax cookies, scoped to / and marked Secure whenever the request arrived over TLS:

CookieLifetimePurpose
khub_access15 minutes (JWT_ACCESS_TTL)A signed JWT carrying the user ID, read on every request by a Passport JWT strategy that extracts it from the cookie (not an Authorization header).
khub_refresh30 days (JWT_REFRESH_TTL)A signed JWT tied to a persisted, revocable session row. POST /api/auth/refresh exchanges it for a new access/refresh pair (rotation on every use); the web client calls this transparently on a 401.
khub_impersonatorSession-admin-onlySet only while a site admin is impersonating another user (see Instance administration); holds the admin's own identity separately from the impersonated session's cookies so the impersonation can be ended and audited correctly.

Refresh tokens are individually revocable — Settings → Security → Sessions lists every active session (by refresh-token identity) and can revoke any of them remotely, which invalidates that khub_refresh cookie's ability to mint new access tokens without affecting other sessions.

Two-factor authentication (TOTP) inserts a short-lived (5 minute), single-purpose intermediate JWT between password verification and issuing real session cookies — the intermediate token authorizes nothing except completing the second factor.

2. Personal access tokens#

For git over HTTPS, the npm/OCI registries, and direct API/MCP access. Created under Settings → Access tokens:

  • Format: khub_pat_ followed by 64 hex characters (32 random bytes). The raw value is shown exactly once at creation and is not recoverable — only a SHA-256 hash and a 12-character prefix (for identifying a token in a list) are persisted.
  • Scopes: repo:read, repo:write, api — the token carries these explicitly rather than inheriting the user's full permission set; a leaked repo:read token cannot push code or call non-Git API endpoints.
  • Creating a token with the api scope requires re-entering your account password in the same request, since that scope grants the broadest surface (including anything reachable via MCP).
  • Optional expiry date; expired tokens are rejected at validation time, not swept — there is nothing to "clean up" for security purposes.

Use it as the password half of HTTP basic auth for Git (https://<username>:<token>@host/...), as the OCI/npm registry password, or as a Bearer token against the REST API and MCP bridge.

3. SSH keys#

Add an Ed25519 or RSA public key under Settings → SSH keys. services/ssh-git authenticates incoming connections against these keys and maps a successful auth to the same user/permission resolution as everything else — there is no separate SSH-specific ACL.

4. Enterprise SSO (SAML 2.0)#

An organization can configure a SAML identity provider under Organization settings → Security. The flow is standard SP-initiated SAML:

  1. 1The user is redirected to GET /api/auth/saml/:orgSlug/login, which redirects to the configured IdP's SSO URL with a signed AuthnRequest.
  2. 2The IdP redirects the browser back to the Assertion Consumer Service endpoint, POST /api/auth/saml/:orgSlug/acs, with a signed SAML Response.
  3. 3KHUB verifies the assertion's signature against the IdP's configured certificate, provisions or matches the user by the assertion's email/NameID, and issues normal khub_access/khub_refresh cookies — from that point on, a SAML-authenticated session is indistinguishable from a password-authenticated one.

The ACS endpoint is deliberately exempted from KHUB's CSRF origin check: the request arrives as a same-origin-looking POST from the IdP's domain, which a strict origin check would reject, but the SAML assertion's own cryptographic signature — not the browser's cookie/origin context — is the actual security boundary for that endpoint.

Request-level identity resolution#

Regardless of which mechanism authenticated the request, every downstream handler receives the same shape: { id, username, isAdmin, impersonatedBy? }. Authorization checks (see Authorization & permissions) never branch on how the request was authenticated — a personal access token with the api scope and a browser session belonging to the same user can do exactly the same things.