BillRun supports authentication against an organization’s directory server using LDAP. This lets users sign in to BillRun with their existing directory credentials (OpenLDAP, Microsoft Active Directory, FreeIPA, and other LDAP v3 servers), and lets the directory’s group memberships decide each user’s BillRun permissions. This feature is available exclusively in the BillRun Enterprise Edition.
LDAP complements OpenID Connect (OIDC). Both protocols can be enabled at the same time.
Unlike OIDC, LDAP does not redirect the user to an external login page. The user types their username and password into the BillRun login screen, and BillRun verifies them directly against the directory.
The login flow:
Integrating BillRun with a directory requires two main steps:
Authentication providers are configured under config.auth.
Config.auth = {
"protocols": [
"ldap"
],
"ldap": [
{
"name": "corp",
"label": "Corporate LDAP",
"uri": "ldaps://ldap.example.com:636",
"base_dn": "dc=example,dc=com",
"bind_dn": "cn=billrun-svc,ou=services,dc=example,dc=com",
"bind_password": "service-account-password",
"user_filter": "(&(objectClass=inetOrgPerson)(|(uid={username})(mail={username})))",
"role_mapping": [
{ "group": "billrun-admins", "roles": ["admin"] },
{ "group": "billrun-billing", "roles": ["read", "write"] },
{ "group": "billrun-viewers", "roles": ["read"] }
]
}
]
}
Config.auth = {
"protocols": [
"oidc",
"ldap"
],
"ldap": [
{
"name": "ad",
"label": "Company Account",
"uri": "ldaps://dc1.corp.example.com:636",
"servers": [
{ "uri": "ldaps://dc1.corp.example.com:636" },
{ "uri": "ldaps://dc2.corp.example.com:636" }
],
"tls_ca_file": "/etc/ssl/certs/corp-root-ca.pem",
"base_dn": "DC=corp,DC=example,DC=com",
"bind_dn": "CN=BillRun Service,OU=Service Accounts,DC=corp,DC=example,DC=com",
"bind_password": "service-account-password",
"user_filter": "(&(objectClass=user)(sAMAccountName={username}))",
"account_domain_name": "corp.example.com",
"account_domain_name_short": "CORP",
"account_canonical_form": 2,
"role_mapping": [
{ "group": "BillRun-Admins", "roles": ["admin"] },
{ "group": "CN=Finance,OU=Groups,DC=corp,DC=example,DC=com", "roles": ["read", "write"] }
]
}
]
}
With this configuration, users can sign in as jdoe, CORP\jdoe or jdoe@corp.example.com. If dc1 is unreachable, dc2 is tried.
| Field | Description |
|---|---|
| protocols | List of authentication protocols enabled for the project. A protocol must be listed here for it to be active and visible in the UI. Add ldap to enable LDAP providers. |
Each entry in the ldap array represents a single directory provider. Several providers can be configured, for example two separate directories. Each one appears as a separate option on the login screen.
| Field | Description |
|---|---|
| name | Unique identifier for the provider configuration. Sent by the login screen as the provider parameter. |
| label | Display name shown on the BillRun login screen, in the “Sign in with” selector. |
| uri | Directory server URL. Use ldaps://host:636 for LDAPS, or ldap://host:389 for plain LDAP or StartTLS. Required unless servers is set. |
| start_tls | (Optional) true to upgrade an ldap:// connection with StartTLS. Defaults to false. |
| tls_verify | (Optional) Verify the server’s TLS certificate. Defaults to true. Set to false only for testing. |
| tls_ca_file | (Optional) Path, on the BillRun server, to a PEM file with the CA certificate(s) that signed the directory’s certificate. Required when the directory uses an internal or self-signed CA. |
| timeout | (Optional) Network and search timeout in seconds. Defaults to 5. |
| servers | (Optional) List of servers to try in order, for failover. Each entry can override any provider field, typically just uri. When omitted, only uri is used. |
| Field | Description |
|---|---|
| base_dn | DN under which users are searched, for example dc=example,dc=com. |
| bind_dn | (Optional, recommended) DN of a read-only service account used to search for the user. If omitted, the search is performed anonymously. |
| bind_password | (Optional) Password of the service account. |
| user_filter | (Optional) LDAP filter used to find the user. {username} is replaced with the value typed on the login screen, safely escaped. Defaults to (&(objectClass=person)(\|(uid={username})(sAMAccountName={username})(mail={username}))). The search must match exactly one entry. |
| user_dn_template | (Optional) Builds the user’s DN directly instead of searching, for example uid={username},ou=people,dc=example,dc=com. Use this when no service account is available. |
| username_attribute | (Optional) User attribute whose value becomes the BillRun username, for example mail. Defaults to the value typed on the login screen. |
| Field | Description |
|---|---|
| account_domain_name | (Optional) Fully qualified AD domain, for example corp.example.com. Enables sign-in as user@corp.example.com. |
| account_domain_name_short | (Optional) NetBIOS domain name, for example CORP. Enables sign-in as CORP\user. |
| account_canonical_form | (Optional) The form the account name is normalized to: 1 DN, 2 username, 3 DOMAIN\username, 4 username@domain. |
| bind_requires_dn | (Optional, advanced) Whether the user must be resolved to a DN before binding. Defaults to true, or false when an AD domain is configured. |
| try_username_split | (Optional, advanced) Whether user@domain and DOMAIN\user are split into user and domain. Defaults to false, or true when an AD domain is configured. Keep it false when users sign in with their e-mail address. |
| Field | Description |
|---|---|
| group_attribute | (Optional) User attribute that lists the user’s group DNs. Defaults to memberOf. Used when group_filter is not set. |
| group_filter | (Optional) Find groups by searching instead of reading group_attribute. {dn} is replaced with the user’s DN and {username} with the login name, for example (&(objectClass=groupOfNames)(member={dn})). |
| group_base_dn | (Optional) DN under which groups are searched. Defaults to base_dn. |
| group_name_attribute | (Optional) Group attribute holding the group name in search results. Defaults to cn. |
| role_mapping | Maps directory groups to BillRun roles. See Role Mapping. |
| default_roles | (Optional) Roles granted to every user who authenticates successfully, in addition to the mapped roles, for example ["read"]. |
Note: The LDAP option will only be displayed on the login screen if ldap is listed in the protocols array.
BillRun permissions come from the user’s directory groups. Each role_mapping entry assigns BillRun roles to the members of one group:
"role_mapping": [
{ "group": "billrun-admins", "roles": ["admin"] },
{ "group": "cn=finance,ou=groups,dc=example,dc=com", "roles": ["read", "write"] }
]
CN part of the DN).default_roles are added for every authenticated user.read, write and admin.The object form { "<group>": [roles] } is also accepted. Prefer the list form above, since group names can contain characters, such as dots, that are not allowed in configuration keys.
Roles are evaluated at every login. Adding or removing a user from a directory group takes effect the next time the user signs in.
Create a dedicated, read-only account for BillRun. It needs permission to:
base_dnmemberOf attribute, or the group entries when group_filter is usedWhen a service account is configured, BillRun also uses it to read the user’s groups after login. Users therefore don’t need permission to read group information themselves.
Create, or choose, a directory group for each permission level, for example billrun-admins, billrun-billing and billrun-viewers. Then reference the groups in role_mapping.
How the user’s groups are found depends on the directory:
| Directory | Recommended setup |
|---|---|
| Active Directory | Default memberOf. For nested groups, set group_filter to (member:1.2.840.113556.1.4.1941:={dn}). |
OpenLDAP with the memberof overlay |
Default memberOf. |
| OpenLDAP without the overlay | group_filter = (&(objectClass=groupOfNames)(member={dn})), or (&(objectClass=posixGroup)(memberUid={username})) for POSIX groups. |
ldap://, passwords are sent unencrypted.tls_ca_file.LDAP does not require a redirect URL to be registered anywhere.
When at least one LDAP provider is configured, the BillRun login screen shows a Sign in with selector above the username field. It lists BillRun account (local users) and each LDAP provider’s label. The user selects the provider and signs in with their directory username and password. The “Forgot your password?” link is hidden for LDAP providers, because passwords are managed in the directory.
The same login is available through the API:
POST https://<billrun-host>/auth/login?protocol=ldap&provider=<provider_name>
Content-Type: multipart/form-data
username=<username>&password=<password>
| Parameter | Description |
|---|---|
| protocol | Must be ldap. |
| provider | Must match the name field defined in the BillRun configuration. |
| username | The user’s directory login: username, e-mail or DOMAIN\user, depending on the configuration. |
| password | The user’s directory password. Empty passwords are always rejected. |
Example: https://billrun.host/auth/login?protocol=ldap&provider=corp
To sign out, call https://<billrun-host>/auth/logout?protocol=ldap.
ldap extension. It is included in the official BillRun Docker images.Failed logins are logged with the prefix AuthLdap:. Common messages:
| Log message | Meaning |
|---|---|
Missing or invalid configuration for provider |
The provider sent at login doesn’t match any name in auth.ldap, or uri is missing. |
Account not found: <username> |
The user search matched no entry. Check base_dn and user_filter. |
Invalid credentials |
Wrong user password, or wrong bind_dn / bind_password for the service account. |
Can't contact LDAP server |
Network or port problem, or a TLS failure. A “certificate verify failed” detail means tls_ca_file is missing or wrong. |
authenticated ... with roles [] |
Login succeeded but no group matched role_mapping. Check the group names and group_attribute / group_filter. |
PHP ldap extension is not loaded |
The BillRun server is missing the PHP ldap extension. |