Authentication
Codumentor supports multiple authentication providers to control access to the API. Authentication is configured through the auth section of codumentor.yaml.
Overview
The auth.provider setting selects which authentication backend to use:
| Provider | Value | Description |
|---|---|---|
| No authentication | "none" | Default. No credentials required. Suitable for local development only. |
| Gitea | "gitea" | Authenticate using Gitea personal access tokens. |
| Local | "local" | Username/password users defined directly in the config file. For small teams and self-hosted deployments without an external directory. |
| LDAP | "ldap" | Authenticate against an LDAP or Active Directory server. |
auth:
provider: "none" # "none", "gitea", "local", or "ldap"
Regardless of provider, set
jwt_secretin any deployment that uses persistent login sessions or the per-user secrets / model-profiles / OAuth plugins — without it sessions reset on restart and those stores are not encrypted at rest.
Separate UI and API providers (advanced).
auth.providerapplies to both the web UI and the API when no per-surface overrides are set. To use different methods for each — e.g. LDAP for interactive UI login andstatic_keyfor OpenAI-compatible API clients — setui_providerand/orapi_provider. Whenapi_provideris omitted it inheritsui_provider(notprovideralone), so hardening the UI also protects the API unless you explicitly setapi_provider(including to"none"). The per-provider config blocks (ldap:,local:, …) are shared between them.
No Authentication (provider: "none")
The default configuration. All API requests are accepted without credentials. This is appropriate for local development and single-user setups where the API is bound to 127.0.0.1.
auth:
provider: "none"
Do not use this setting in production when the API is accessible over a network. See the Security guide for recommendations.
Gitea (provider: "gitea")
Users authenticate by providing a Gitea personal access token. Codumentor validates the token against the Gitea API on each request and caches successful validations to reduce API calls.
Configuration
auth:
provider: "gitea"
gitea:
base_url: "https://gitea.example.com"
token_cache_ttl_minutes: 30
| Setting | Required | Default | Description |
|---|---|---|---|
base_url | Yes | -- | Base URL of the Gitea instance |
token_cache_ttl_minutes | No | 30 | How long (in minutes) to cache a successful token validation. Set to 0 to disable caching. Recommended range for production: 15--120 minutes. |
How It Works
- The client sends a Gitea personal access token in the
Authorizationheader. - Codumentor calls the Gitea API (
/api/v1/user) to validate the token. - On success, the result is cached for
token_cache_ttl_minutesto avoid repeated API calls. - Invalid tokens are never cached.
Generating a Token
Users create personal access tokens in their Gitea account under Settings > Applications > Generate New Token. The token only needs the read:user scope.
Local (provider: "local")
A fixed set of username/password users defined directly in codumentor.yaml. This is the simplest way to give a small team real logins without standing up Gitea or LDAP — it is what the self-hosted reference configs use.
Configuration
auth:
provider: "local"
jwt_secret: "${CODUMENTOR_JWT_SECRET}" # see below — needed for persistent sessions
local:
users:
- username: admin
password: "${CODUMENTOR_ADMIN_PASSWORD}"
email: admin@example.com
display_name: "Site Admin"
roles: ["admin", "user", "power_user"]
- username: dev
password: "${CODUMENTOR_DEV_PASSWORD}"
roles: ["user"]
| Field | Required | Default | Description |
|---|---|---|---|
username | Yes | -- | Login name. Must be unique across the users list (duplicates fail config validation). |
password | Yes | -- | Plaintext or a pbkdf2_sha256$... hash (see below). Supports ${ENV_VAR} substitution so the real secret stays out of the committed file. |
email | No | null | Used for identity matching and notifications. |
display_name | No | null | Shown in the UI. Falls back to username. |
roles | No | ["user"] | Codumentor roles granted on login. Common roles: user, power_user, admin. |
Passwords: env injection and hashing
Never commit a plaintext password. Use ${ENV_VAR} substitution and provide the value at runtime (systemd EnvironmentFile, .env, secrets manager):
export CODUMENTOR_ADMIN_PASSWORD='s3cret'
export CODUMENTOR_DEV_PASSWORD='…'
For defence in depth you can store a PBKDF2 hash instead of a plaintext value (the provider accepts either, auto-detected by the pbkdf2_sha256$ prefix). Generate one with:
python -c "from codumentor.auth.providers.local import hash_password; print(hash_password('s3cret'))"
Paste the resulting pbkdf2_sha256$<iterations>$<salt>$<hash> string as the password value (this can be committed safely).
Security note. As with every provider, do not expose a
provider: "none"/localinstance on a public network without a reverse proxy and TLS. See Security. Local auth has no lockout or rate limiting of its own — put rate limiting on the reverse proxy.
Session Persistence and At-Rest Encryption (jwt_secret)
auth.jwt_secret is a single deployment-wide secret that does two things:
- Signs login session tokens. Without it, sessions are signed with an ephemeral key generated at startup — so every restart invalidates all logins and forces users to sign in again.
- Seeds the at-rest encryption key (Fernet) for the per-user stores written by the
user_secrets,model_profiles, andoauthplugins. Without it, those credentials are stored unencrypted on disk.
auth:
jwt_secret: "${CODUMENTOR_JWT_SECRET}"
# Generate a strong value once and keep it stable for the life of the deployment:
export CODUMENTOR_JWT_SECRET="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')"
If the referenced environment variable is unset, Codumentor refuses to use the literal ${CODUMENTOR_JWT_SECRET} placeholder as the key (it would be a guessable, committed value). It drops the secret, logs a prominent ERROR, and degrades to ephemeral sessions + unencrypted secret stores. Treat that log line as a misconfiguration to fix, not a warning to ignore.
Rotation. Changing jwt_secret logs everyone out and makes existing encrypted rows unreadable — unless you let the affected plugins decrypt under the old key while re-encrypting under the new one. Pass the prior value(s) to each store plugin via its previous_jwt_secrets arg; rotated rows are re-encrypted lazily on next read:
plugins:
- module: codumentor.plugins.user_secrets
class: UserSecretsPlugin
args:
enabled: true
previous_jwt_secrets: ["${CODUMENTOR_JWT_SECRET_OLD}"]
(The same previous_jwt_secrets arg is accepted by the model_profiles and oauth plugins.)
LDAP (provider: "ldap")
For enterprise environments, Codumentor can authenticate users against an LDAP directory or Active Directory server. This provider supports user search, group membership checks, and role mapping.
Configuration
auth:
provider: "ldap"
ldap:
# Connection (required)
server_url: "ldaps://ldap.example.com"
bind_dn: "cn=codumentor-svc,ou=services,dc=example,dc=com"
bind_password: "${LDAP_BIND_PASSWORD}"
base_dn: "dc=example,dc=com"
# Server type: "active_directory", "openldap", or "generic"
server_type: "openldap"
# Security: "ldaps", "starttls", or "none"
security_mode: "ldaps"
# User search
user_search_filter: "(&(objectClass=inetOrgPerson)(uid={username}))"
user_search_base: "ou=users,dc=example,dc=com"
username_attribute: "uid"
email_attribute: "mail"
display_name_attribute: "displayName"
# Group settings
group_search_base: "ou=groups,dc=example,dc=com"
group_member_attribute: "memberOf"
# Role mapping
group_role_mapping:
"cn=admins,ou=groups,dc=example,dc=com": ["admin"]
"cn=developers,ou=groups,dc=example,dc=com": ["developer"]
default_roles: ["user"]
# Access restriction (optional)
require_group: "cn=codumentor-users,ou=groups,dc=example,dc=com"
# SSL certificate validation
validate_cert: true
ca_cert_file: "/etc/ssl/certs/ca-certificates.crt"
Connection Settings
| Setting | Required | Default | Description |
|---|---|---|---|
server_url | Yes | -- | LDAP server URL. Use ldaps:// for SSL or ldap:// for plain/STARTTLS. |
bind_dn | Yes | -- | Distinguished name of the service account used for searching. |
bind_password | Yes | -- | Password for the bind DN. Use ${LDAP_BIND_PASSWORD} to read from an environment variable. |
base_dn | Yes | -- | Base DN for all LDAP searches. |
Server Type
The server_type setting applies sensible defaults for common directory servers:
| Type | username_attribute | user_search_filter | Notes |
|---|---|---|---|
active_directory | sAMAccountName | (&(objectClass=user)(sAMAccountName={username})) | Uses AD-specific nested group resolution |
openldap | uid | (&(objectClass=inetOrgPerson)(uid={username})) | Standard LDAP schema, no nested group resolution |
generic | uid | (&(objectClass=person)(uid={username})) | Minimal defaults, configure manually |
You can override any of the defaults by specifying the corresponding setting explicitly.
Security Mode
| Mode | Description |
|---|---|
ldaps | SSL/TLS on connect (port 636). Recommended for production. |
starttls | Upgrade plain connection to TLS after connect (port 389). |
none | No encryption. Only use for testing on a trusted network. |
User Search Settings
| Setting | Default | Description |
|---|---|---|
user_search_filter | Depends on server_type | LDAP filter to locate the user. {username} is replaced with the login name. |
user_search_base | Value of base_dn | Subtree to search for users. |
username_attribute | Depends on server_type | LDAP attribute containing the username. |
email_attribute | mail | LDAP attribute containing the email address. |
display_name_attribute | displayName | LDAP attribute containing the display name. |
Group and Role Mapping
| Setting | Default | Description |
|---|---|---|
group_search_base | Value of base_dn | Subtree to search for groups. |
group_member_attribute | memberOf | LDAP attribute on the user entry listing group membership. |
group_role_mapping | {} | Dictionary mapping LDAP group DNs to lists of Codumentor roles. |
default_roles | ["user"] | Roles assigned when the user does not match any group mapping. |
require_group | null | If set, only users who are members of this group can authenticate. All others are rejected. |
SSL Certificate Settings
| Setting | Default | Description |
|---|---|---|
validate_cert | true | Whether to validate the server's TLS certificate. Set to false only for testing. |
ca_cert_file | null | Path to a custom CA certificate file. If not set, the system certificate store is used. |
Full LDAP Example (Active Directory)
auth:
provider: "ldap"
ldap:
server_url: "ldaps://dc01.corp.example.com"
bind_dn: "CN=Codumentor Service,OU=Service Accounts,DC=corp,DC=example,DC=com"
bind_password: "${LDAP_BIND_PASSWORD}"
base_dn: "DC=corp,DC=example,DC=com"
server_type: "active_directory"
security_mode: "ldaps"
user_search_base: "OU=Employees,DC=corp,DC=example,DC=com"
# sAMAccountName and AD-specific filter are applied automatically
group_search_base: "OU=Groups,DC=corp,DC=example,DC=com"
group_member_attribute: "memberOf"
group_role_mapping:
"CN=Codumentor Admins,OU=Groups,DC=corp,DC=example,DC=com": ["admin"]
"CN=Engineering,OU=Groups,DC=corp,DC=example,DC=com": ["developer"]
default_roles: ["user"]
require_group: "CN=Codumentor Users,OU=Groups,DC=corp,DC=example,DC=com"
validate_cert: true
ca_cert_file: "/etc/ssl/certs/corp-ca.pem"
Full LDAP Example (OpenLDAP)
auth:
provider: "ldap"
ldap:
server_url: "ldaps://ldap.example.com"
bind_dn: "cn=codumentor,ou=services,dc=example,dc=com"
bind_password: "${LDAP_BIND_PASSWORD}"
base_dn: "dc=example,dc=com"
server_type: "openldap"
security_mode: "ldaps"
user_search_filter: "(&(objectClass=inetOrgPerson)(uid={username}))"
user_search_base: "ou=people,dc=example,dc=com"
username_attribute: "uid"
email_attribute: "mail"
display_name_attribute: "cn"
group_search_base: "ou=groups,dc=example,dc=com"
group_member_attribute: "memberOf"
group_role_mapping:
"cn=admins,ou=groups,dc=example,dc=com": ["admin"]
"cn=dev-team,ou=groups,dc=example,dc=com": ["developer"]
default_roles: ["user"]
validate_cert: true
Multiple LDAP Directories
When users live in more than one independent directory (separate domains, an
employee directory plus a contractor directory, partner organisations, etc.),
configure them as a list under ldap_directories instead of the single
ldap: block. The two forms are mutually exclusive.
auth:
provider: "ldap"
ldap_directories:
- name: "corp"
server_url: "ldaps://corp.example.com"
bind_dn: "cn=svc,dc=corp,dc=example,dc=com"
bind_password: "${LDAP_CORP_BIND_PASSWORD}"
base_dn: "dc=corp,dc=example,dc=com"
server_type: "active_directory"
group_role_mapping:
"CN=Codumentor Admins,OU=Groups,DC=corp,DC=example,DC=com": ["admin"]
- name: "contractors"
server_url: "ldaps://contractors.example.com"
bind_dn: "cn=svc,dc=contractors,dc=example,dc=com"
bind_password: "${LDAP_CONTRACTORS_BIND_PASSWORD}"
base_dn: "dc=contractors,dc=example,dc=com"
server_type: "openldap"
# No admin mapping — contractors get the default role only.
Each entry accepts the full set of LDAP options listed above. Each entry must
have a unique, non-empty name.
How login routing works
On every login Codumentor tries each directory in declared order and stops at
the first directory that authenticates the user successfully:
- If a directory responds with user not found, wrong password, disabled account, or not in the required group, the next directory is tried.
- If a directory is unreachable (network error, bind failure), the next directory is tried and a warning is logged. A single dead replica does not lock everyone out.
- If all directories return a connection-level failure, the user sees the generic "service unavailable, try again" response.
- If any directory responded but none accepted the credentials, the user sees the generic "invalid username or password" response. The login form does not reveal which directory rejected the user.
The first directory whose bind succeeds wins, so if the same username exists
in two directories, the one listed first authenticates. Order matters.
Identity across directories
User.provider remains "ldap" regardless of which directory handled the
login. With the default trust_email behaviour, a user who appears in two
directories with the same verified email is treated as the same person — they
land on a single user record.
Audit logs
Each per-directory attempt is logged with provider="ldap:<name>" (e.g.
ldap:corp, ldap:contractors) so administrators can see which directory
authenticated a given login or which one rejected it.
See Also
- Configuration -- Full configuration reference including the
authsection - Security -- Security best practices for production deployments