Truke KF resolves every request — a browser session, a single sign-on login, a request behind a corporate proxy, or an API call — to one user record in the KF database. That record carries the user's access labels (ACL). Authentication is how you prove who you are; the ACL is what you are allowed to see. The two are kept separate: no matter how you log in, access always comes from your KF user record.
Identity. Each user has a stable uid (a short, space-free identifier — never reused, even after a user is removed), a display name, an email (always present, used for notifications), and an ACL label string. See access control for what the labels mean.
Login channels. There are four ways to authenticate. They can be enabled independently and used side by side:
| Channel | Who it is for | How it works |
|---|---|---|
| Local | small teams, the default | username + password stored in the KF database |
| OIDC | one modern identity provider (Google, Entra, Okta, Keycloak) | single sign-on; KF talks to the provider directly |
| Header | enterprises with SAML / LDAP / MFA | a trusted reverse proxy authenticates and passes the identity to KF |
| Token | API and MCP clients | a personal access token sent as `Authorization: Bearer …` |
Authorization is separate. None of these channels grant access on their own. A new user provisioned through SSO starts with whatever the default ACL policy says (by default, no labels — they see only unrestricted items) until an administrator grants labels. Identity providers never decide access directly.
All authentication is configured in one `[auth]` section of kf.ini. Unlike the
other sections, `[auth]` is written as an indented tree (the first line after the
header has no `=`):
[auth] session cookie_name kf_session ttl 24h secret_env KF_SESSION_SECRET local enabled true oidc enabled false header enabled false token enabled true groups enabled false
Secrets are never written in kf.ini. Every secret is referenced by the name
of an environment variable, through a key ending in _env. KF reads the value from
the environment at start-up. For example `secret_env KF_SESSION_SECRET` means KF
will use whatever KF_SESSION_SECRET is set to:
export KF_SESSION_SECRET="$(openssl rand -hex 32)"
If you enable a channel but leave a required field empty, KF refuses to start and prints what is missing — there is no silent half-configured mode.
This is the default and needs no setup. Credentials live in the KF database. Each user has a username, a password, and an ACL label string. Add, change, or remove users through the administration interface at `/admin/users`.
Local login is on unless you turn it off:
local enabled true
Users created by SSO have no password and cannot log in with the password form; they must use their single sign-on channel.
OIDC delegates authentication to one external identity provider (IdP) — Google, Microsoft Entra, Okta, Keycloak, and similar. The user logs in at the provider; KF never sees the password. The login page offers both the password form and a single sign-on button, so local and SSO users coexist.
oidc enabled true issuer "https://accounts.google.com" client_id kf-app client_secret_env KF_OIDC_SECRET redirect_url "https://kf.example.com/oidc/callback" jit true default_acl "" uid_claim preferred_username subject_claim sub
| Key | Required | Description |
|---|---|---|
issuer | yes | Provider base URL — KF discovers the endpoints at `…/.well-known/openid-configuration` |
client_id | yes | Client ID issued by the provider |
client_secret_env | yes | Name of the env var holding the client secret |
redirect_url | yes | Must exactly match the callback registered at the provider: https://kf.example.com/oidc/callback |
jit | no | Auto-create users on first login (see below); default false |
default_acl | no | Labels given to auto-created users; default empty (no labels) |
uid_claim | no | Token claim used to derive the uid; default preferred_username |
subject_claim | no | Stable claim used to match returning users; default sub |
Set the secret in the environment before starting KF:
export KF_OIDC_SECRET="GOCSPX-…"
With `jit true`, a user who logs in for the first time is created automatically.
KF derives a uid from uid_claim (or the email local-part) and matches
returning users on the provider's stable subject_claim — never on email alone, so
a changed email address does not create a duplicate or hijack another account. The
new user receives the default_acl labels (none, by default). Their name and email
are kept in sync from the provider on every login; their ACL is not touched by
SSO — it stays under KF's control.
With `jit false`, only users that already exist in the KF database may log in; unknown users are rejected. To pre-provision, add the user in `/admin/users` and leave the password empty.
# Google issuer "https://accounts.google.com" # Microsoft Entra (Azure AD) issuer "https://login.microsoftonline.com//v2.0" # Keycloak issuer "https://keycloak.example.com/realms/ "
Register https://kf.example.com/oidc/callback as the redirect URI at the provider,
and copy the client ID and secret into kf.ini / the environment.
KF deliberately carries no SAML, LDAP, Kerberos, or MFA code. When you need any of those, put a reverse proxy (nginx, Caddy, or Apache) with an authentication module in front of KF. The proxy handles the enterprise protocol, then forwards the verified identity to KF as HTTP headers.
header enabled true user_header X-Authenticated-User email_header X-Authenticated-Email name_header X-Authenticated-Name groups_header X-Authenticated-Groups secret_header X-Auth-Secret shared_secret_env KF_HEADER_SECRET trusted_proxy 127.0.0.1 jit true default_acl ""
How KF stays safe when it trusts headers:
X-Auth-Secret) carrying a
value only it and KF know (shared_secret_env). A request whose secret is missing
or wrong is rejected with 403, no matter what identity headers it carries. This
is what stops a user from forging X-Authenticated-User.trusted_proxy address.127.0.0.1:8080, or a firewalled private interface). If KF is directly reachable,
the header channel is forgeable.Generate the secret and share it on both sides:
export KF_HEADER_SECRET="$(openssl rand -hex 32)"
A minimal nginx terminator (paired with an OIDC/SAML auth module such as oauth2-proxy) injects the verified identity and the secret on every request, and strips any client-supplied copy:
location / {
auth_request /oauth2/auth;
auth_request_set $kf_user $upstream_http_x_auth_request_preferred_username;
auth_request_set $kf_email $upstream_http_x_auth_request_email;
proxy_set_header X-Authenticated-User $kf_user;
proxy_set_header X-Authenticated-Email $kf_email;
proxy_set_header X-Auth-Secret "REPLACE_WITH_SECRET";
proxy_pass http://127.0.0.1:8080;
}
The SAML/LDAP/MFA flow lives entirely in the proxy; KF only ever sees the headers.
Provisioning works the same as OIDC: with `jit true`, users are created on first
arrival with the default_acl policy.
To confirm the boundary, a forged request with no secret must never log you in:
curl -is https://kf.example.com/ -H 'X-Authenticated-User: attacker' | head -n1
For programmatic access — scripts, integrations, and MCP clients — KF issues personal access tokens. A token is bound to your user and carries exactly your access; it grants nothing extra.
token enabled true
Create a token (you must be logged in). The plaintext is returned once — store it now, it cannot be retrieved later:
curl -X POST https://kf.example.com/auth/tokens \
-d name=ci-pipeline -d ttl=720h
# {"id":"a1b2c3d4","token":"f0e1d2…"}
Use a token by sending it as a bearer credential:
curl https://kf.example.com/mcp -H "Authorization: Bearer f0e1d2…"
Revoke a token using the id returned at creation:
curl -X POST https://kf.example.com/auth/tokens/a1b2c3d4/revoke
Tokens are stored only as a hash — KF never keeps the plaintext — and an expired or
revoked token is refused with 401. The MCP endpoint (`/mcp`) and the token
endpoints require authentication; they are never served anonymously.
Normally an administrator assigns ACL labels. If you would rather drive the initial labels from your identity provider's groups, enable the group map. It translates IdP group names to KF labels when a user is first provisioned; groups with no entry are ignored. This is a mapping you define in KF — raw provider groups are never trusted directly.
groups
enabled true
map
QA-Automotive automotive
Engineering eng
A user whose provider groups include QA-Automotive is created with the
managed in KF and is not overwritten on later logins. ## Sessions and secrets After a successful local or SSO login, KF sets a signed session cookie (`cookie_name`, default `kf_session`) so later requests do not repeat the login. Sign the cookie with a stable secret from the environment so sessions survive a restart:
session cookie_name kf_session ttl 24h secret_env KF_SESSION_SECRET
export KF_SESSION_SECRET="$(openssl rand -hex 32)"
Header and token requests do not use the session cookie — they carry their identity
on every request.
## Quick reference
| Path | Purpose |
|------|---------|
| `/oidc/login` | Start a single sign-on login |
| `/oidc/callback` | OIDC return URL (register this at your provider) |
| `/auth/tokens` | `POST` to create a personal access token |
| `/auth/tokens/{id}/revoke` | `POST` to revoke a token |
| `/mcp` | MCP endpoint (requires a token or session) |
| `/admin/users` | Manage local users and ACL labels |