Skip to content

Single Sign On

zach115th edited this page Aug 25, 2026 · 1 revision

Single Sign-On

Authenticating users against an external identity provider — OIDC or LDAP/Active Directory — instead of local IRIS accounts.

SSO is configured only in .env. There is no GUI for it. If you are looking for OIDC settings under Advanced → Server Settings, they are not there and are not missing by oversight — see Why SSO is not in the GUI.

SSO replaces the login method. Multi-factor authentication is a separate feature, toggled in the interface, and is documented in Multi-Factor Authentication. Note that OIDC logins bypass IRIS MFA entirely — with SSO you enforce a second factor at your identity provider.


Choosing an authentication type

One setting selects the mechanism, read at process start:

IRIS_AUTHENTICATION_TYPE=local     # local | ldap | oidc | oidc_proxy
Value What it does
local Username and password checked against the IRIS database. The default.
ldap Password checked by binding to an LDAP/AD server.
oidc Full OIDC authorization-code flow — iris-ng redirects to your IdP. This is normal SSO.
oidc_proxy For deployments already behind an authenticating reverse proxy (oauth2-proxy). iris-ng trusts the proxy and introspects its token.

AuthenticationType in configuration.py is an enum listing only local and oidc_proxy. It is vestigial — the real dispatch is a string comparison, and all four values above work.

Environment variable naming

Configuration is read in this order: Azure Key Vault → environment variable → config.ini. Each setting belongs to a section, and the environment variable name is <SECTION>_<OPTION>.

This matters because it is easy to get wrong, and a misnamed variable is silently ignored — it falls through to the default rather than raising:

Section / option Environment variable
OIDC / ISSUER_URL OIDC_ISSUER_URL
LDAP / SERVER LDAP_SERVER
IRIS / AUTHENTICATION_LOCAL_FALLBACK IRIS_AUTHENTICATION_LOCAL_FALLBACK
IRIS / NEW_USERS_DEFAULT_GROUP IRIS_NEW_USERS_DEFAULT_GROUP
IRIS / AUTHENTICATION_CREATE_USER_IF_NOT_EXIST IRIS_AUTHENTICATION_CREATE_USER_IF_NOT_EXIST

The upstream DFIR-IRIS documentation writes the fallback setting as AUTHENTICATION_LOCAL_FALLBACK, without the IRIS_ prefix. That name does not work — the loader looks for IRIS_AUTHENTICATION_LOCAL_FALLBACK.

Keep a way back in

IRIS_AUTHENTICATION_LOCAL_FALLBACK (default True) keeps the local username/password form available when the external provider fails or does not recognise the account.

Leave it enabled until SSO is proven working. With it set to False and oidc, /login redirects straight to the IdP, so a broken IdP configuration means nobody can log in — including the administrator who would fix it. Because the setting lives in .env rather than the database, recovery is always possible without being logged in: edit the file and recreate the container.


OIDC

1. Register iris-ng with your identity provider

Create a confidential client (web application) with the authorization-code flow.

The redirect URI is built from the request host, so it is your instance's external URL plus /oidc-authorize:

https://iris.example.com/oidc-authorize

Register that exact value. The login entry point is /oidc-login.

2. Configure iris-ng

IRIS_AUTHENTICATION_TYPE=oidc

OIDC_ISSUER_URL=https://idp.example.com/realms/dfir
OIDC_CLIENT_ID=iris-ng
OIDC_CLIENT_SECRET=<client secret>

# Optional — override only if your IdP does not publish them at the issuer
OIDC_AUTH_ENDPOINT=
OIDC_TOKEN_ENDPOINT=
OIDC_END_SESSION_ENDPOINT=

# Optional — defaults shown
OIDC_SCOPES=openid email profile
OIDC_MAPPING_USERNAME=preferred_username
OIDC_MAPPING_EMAIL=email

# Keep the local login form available while you test
IRIS_AUTHENTICATION_LOCAL_FALLBACK=True

# Create IRIS accounts for IdP users on first login
IRIS_AUTHENTICATION_CREATE_USER_IF_NOT_EXIST=True

Apply with a recreate, not a restart — docker restart does not re-read env_file:

docker compose -f docker-compose.dev.yml up -d --force-recreate --no-deps app worker ai_worker

Confirm what the app actually parsed:

docker logs iriswebapp_app 2>&1 | grep -i "Authentication mechanism\|local fallback\|Create user"

3. Claim mapping

Two claims are read from the ID token, each falling back to the other if absent:

  • loginOIDC_MAPPING_USERNAME, else the email claim
  • display nameOIDC_MAPPING_EMAIL, else the username claim

The resulting login is matched against the existing IRIS user column. An account created locally with the same login is adopted rather than duplicated, which is how you migrate existing analysts onto SSO without losing case attribution.

preferred_username suits Keycloak. Entra ID (Azure AD) does not emit it by default — use OIDC_MAPPING_USERNAME=email, or add an optional claim in the app registration.

4. Auto-provisioned OIDC users land with no group

This is the step people miss. With IRIS_AUTHENTICATION_CREATE_USER_IF_NOT_EXIST=True, a first-time IdP user gets an IRIS account created automatically — added to organisation 1, with a random unusable password, and no group.

Default access in IRIS-NG is deny_all, so that user logs in successfully and then sees nothing at all: no cases, an empty dashboard. It reads like a broken install rather than a permissions gap.

IRIS_NEW_USERS_DEFAULT_GROUP (default Analysts) does not help here — it is applied only on the LDAP provisioning path, not the OIDC one. After a user's first login, assign a group under Advanced → Access Control → Users.

If you would rather not have surprise accounts appear, set IRIS_AUTHENTICATION_CREATE_USER_IF_NOT_EXIST=False. Unknown users are then rejected with User not found in IRIS, and you create accounts ahead of time with logins that match the IdP claim.

OIDC behind an authenticating proxy

Use oidc_proxy only when something in front of iris-ng (oauth2-proxy, for example) has already authenticated the request:

IRIS_AUTHENTICATION_TYPE=oidc_proxy
OIDC_IRIS_DISCOVERY_URL=https://idp.example.com/realms/dfir/.well-known/openid-configuration
OIDC_IRIS_CLIENT_ID=iris-ng
OIDC_IRIS_CLIENT_SECRET=<client secret>
OIDC_IRIS_ADMIN_ROLE_NAME=iris-admin
OIDC_IRIS_INIT_ADMINISTRATOR_EMAIL=admin@example.com
OIDC_IRIS_AUDIENCE=
OIDC_IRIS_TOKEN_VERIFY_MODE=signature

Note the distinct OIDC_IRIS_* names — this mode does not reuse the OIDC_* names above.

This mode fetches the discovery URL at startup and calls exit(0) if that fails. The container stops rather than starting with degraded auth. If the app exits immediately after enabling oidc_proxy, check the log for OIDC ERROR before suspecting anything else — an unreachable IdP or an untrusted TLS chain both land here. Set TLS_ROOT_CA to a CA bundle path if your IdP uses an internal CA.


LDAP / Active Directory

IRIS_AUTHENTICATION_TYPE=ldap

LDAP_SERVER=dc01.example.com
LDAP_PORT=636
LDAP_AUTHENTICATION_TYPE=SIMPLE        # or NTLM
LDAP_USER_PREFIX=uid=
LDAP_USER_SUFFIX=ou=people,dc=example,dc=com

LDAP_USE_SSL=True
LDAP_VALIDATE_CERTIFICATE=True
LDAP_TLS_VERSION=1.2                   # 1.0 | 1.1 | 1.2 only

IRIS_AUTHENTICATION_LOCAL_FALLBACK=True

The bind DN is assembled as <USER_PREFIX><username>,<USER_SUFFIX>. With LDAP_AUTHENTICATION_TYPE=NTLM the username is passed through unmodified, so use DOMAIN\user form and leave the prefix and suffix empty.

LDAP_TLS_VERSION accepts only 1.0, 1.1 or 1.2 — anything else raises at startup. There is no TLS 1.3 option.

Certificates are filenames, not paths

When LDAP_USE_SSL=True, these are resolved relative to certificates/ldap/ inside the container, and each is checked for existence at startup:

LDAP_SERVER_CERTIFICATE=ldap-server.pem
LDAP_CA_CERTIFICATE=ca.pem
LDAP_PRIVATE_KEY=client-key.pem
LDAP_PRIVATE_KEY_PASSWORD=
LDAP_CUSTOM_TLS_CONFIG=True

Giving an absolute path fails: the app looks for certificates/ldap/<the whole string you provided>. Mount the files into certificates/ldap/ and reference them by bare filename.

LDAP user provisioning

To create IRIS accounts for directory users on first login, these are required and are validated at startup:

IRIS_AUTHENTICATION_CREATE_USER_IF_NOT_EXIST=True
LDAP_SEARCH_DN=ou=people,dc=example,dc=com
LDAP_ATTRIBUTE_IDENTIFIER=uid            # sAMAccountName on AD
LDAP_ATTRIBUTE_DISPLAY_NAME=displayName  # optional
LDAP_ATTRIBUTE_MAIL=mail                 # optional
IRIS_NEW_USERS_DEFAULT_GROUP=Analysts

Unlike OIDC, the LDAP path does put new users in IRIS_NEW_USERS_DEFAULT_GROUP, so they arrive with working access. If LDAP_ATTRIBUTE_MAIL is unset, the address is synthesised as <login>@ldap to satisfy the uniqueness constraint.

LDAP logins are not exempt from IRIS MFA — the two compose, and enrolment confirms the user's password against the directory. See Multi-Factor Authentication.


Why SSO is not in the GUI

The MFA toggle is a database column, so it can live on the settings page. The SSO settings cannot, for three reasons:

  1. They are read at module import, before the database connection exists — the same file supplies the Postgres credentials used to build that connection.
  2. oidc_proxy performs network I/O at import time, fetching the discovery document before the application starts.
  3. They include secrets. The server-settings model is serialised by its schema and returned by the settings endpoint, so an IdP client secret stored there would be readable through the API. This is the same reasoning that keeps SECRET_KEY in a separate table.

There is also a safety argument for leaving at least the authentication type in a file: a bad auth configuration saved through the browser can lock out every account, and a file-based setting can always be fixed without logging in.


Troubleshooting

Symptom Cause
App exits right after enabling oidc_proxy Discovery URL unreachable or untrusted TLS. configuration.py calls exit(0) on failure. docker logs iriswebapp_app | grep "OIDC ERROR".
SSO login works, user sees no cases Auto-provisioned OIDC user has no group. Assign one under Access Control → Users.
User not found in IRIS after IdP login IRIS_AUTHENTICATION_CREATE_USER_IF_NOT_EXIST is false, or the username claim does not match the IRIS login.
User not active in IRIS The account exists but is disabled in IRIS. The IdP does not re-enable it.
Setting appears ignored Wrong variable name. The pattern is <SECTION>_<OPTION> — note IRIS_AUTHENTICATION_LOCAL_FALLBACK carries the prefix. A misnamed variable falls through to the default silently.
.env edit had no effect docker restart does not re-read env_file. Use up -d --force-recreate --no-deps app worker ai_worker.
Unable to read LDAP certificate file The value must be a bare filename resolved under certificates/ldap/, not an absolute path.
Unsupported LDAP TLS version Only 1.0, 1.1, 1.2 are accepted.
MFA prompt never appears for SSO users Expected — OIDC logins skip MFA. Enforce it at the IdP.

Check what the application actually parsed at startup:

docker logs iriswebapp_app 2>&1 | grep -iE "Authentication mechanism|local fallback|Create user during"

Related pages

Clone this wiki locally