> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/developer-guides/architecture-decision-log/adr-0074-pluggable-auth-modes.md).

# ADR-0074: Authentication is a pluggable mode selected by auth.type, defaulting to DISABLED

## Status

**Accepted.** Reconstructed from the codebase on 2026-05-31; the decision is live in the source today.

## Context

ODD runs in very different settings — a developer evaluating it on a laptop, a small team behind a VPN, an enterprise with OAuth/SSO or a corporate LDAP. The platform has to support all of them without compiling in one authentication scheme, and without forcing a first-time evaluator to stand up an identity provider just to start the app. The design questions are: how to make authentication a *deployment* choice rather than a code property, and what to ship as the default.

## Decision

**Authentication is a mode selected by the `auth.type` property, and each mode is a mutually-exclusive, fully-formed `SecurityWebFilterChain`.** Four `@Configuration` classes each carry `@ConditionalOnProperty("auth.type", havingValue=<MODE>)`, so the value of `auth.type` decides — *by construction* — which single filter chain is built at boot. The modes cannot overlap or partially combine; there is always exactly one active chain:

* **DISABLED** — `anyExchange().permitAll()`: no authentication, and the permission rules of ADR-0002 are not wired in. Every endpoint is open.
* **LOGIN\_FORM** — a static username/password list with form login; a whitelist plus `pathMatchers("/**").authenticated()`.
* **OAUTH2** — OIDC/OAuth2 client login, with group→role mapping.
* **LDAP** — LDAP / Active-Directory bind, with group→role mapping.

**`auth.type` ships as `DISABLED`.**

Service-to-service (S2S) authentication is **not a fifth mode** — it composes *additively*. Each of the three real modes layers the `S2sAuthenticationFilter` onto its chain when `auth.s2s.enabled` is set; DISABLED, having no chain to protect, does not. CSRF protection is disabled in every mode's chain — a cross-mode convention for this SPA/token-style backend, not a per-mode choice.

## Consequences

* **DISABLED is for local evaluation only.** In DISABLED mode the platform performs no authentication and enforces none of the ADR-0002 permission rules — every endpoint, including every mutation, is open to anyone who can reach the port. It exists so the platform runs with zero security configuration on a developer's machine. **Any networked or production deployment must set `auth.type` to `LOGIN_FORM`, `OAUTH2`, or `LDAP`** — leaving it at the default on a reachable network exposes the whole API. The [security-configuration guide](/configuration-and-deployment/enable-security.md) is the operator's path to enabling a mode.
* **LOGIN\_FORM authenticates but does not authorize per user.** Every configured form-login credential is granted the `ADMIN` authority, so LOGIN\_FORM fits a small trusted team rather than a deployment that needs role separation; `OAUTH2` and `LDAP` are the modes that carry real group→role mapping.
* **Adding an auth mode means adding one `@Configuration`.** A new scheme is a new conditionally-loaded filter-chain bean, not a branch inside an existing one — the modes stay isolated and individually readable, at the cost of some duplicated chain wiring across the four classes.
* **S2S being additive** lets a deployment accept both interactive logins (its chosen mode) and service tokens at once, keyed independently — rather than forcing a choice between human and machine callers.

## Evidence

* `odd-platform-api/.../config/DisabledAuthSecurityConfiguration.java:10-18` — `@ConditionalOnProperty("auth.type", havingValue="DISABLED")`; the chain is `.csrf(disable).authorizeExchange(anyExchange().permitAll())` — open by construction.
* `odd-platform-api/src/main/resources/application.yml:32-34` — `auth: type: DISABLED`, the shipped default.
* `odd-platform-api/.../config/LoginFormSecurityConfiguration.java:31` (`havingValue="LOGIN_FORM"`), `:53-59` (whitelist + `pathMatchers("/**").authenticated()` + form login); `:81` builds each credential with `getAuthorities(true)`, which `auth/mapper/GrantedAuthorityExtractor.java:12-16` resolves to the `ADMIN` authority.
* `odd-platform-api/.../config/OAuthSecurityConfiguration.java:71` and `.../config/LDAPSecurityConfiguration.java:51` — the `havingValue="OAUTH2"` and `"LDAP"` chains; the four `@ConditionalOnProperty("auth.type", …)` beans are mutually exclusive.
* `.../config/LoginFormSecurityConfiguration.java:61-63`, `.../config/OAuthSecurityConfiguration.java:109`, `.../config/LDAPSecurityConfiguration.java:150` — each real mode adds `s2sAuthenticationFilter` only when `auth.s2s.enabled` (S2S is additive); DISABLED does not. `DisabledAuthSecurityConfiguration.java:15`, `LoginFormSecurityConfiguration.java:54`, `OAuthSecurityConfiguration.java:96`, `LDAPSecurityConfiguration.java:143` — CSRF disabled in all four.

## See also

* [ADR-0002 — Centralised path-matcher authorization](/developer-guides/architecture-decision-log/adr-0002-centralised-path-matcher-authorization.md) — the permission rules the authenticated modes enforce, and which DISABLED leaves unwired.
* [ADR-0003 — The catalog is read-collaborative](/developer-guides/architecture-decision-log/adr-0003-read-collaborative-authorization.md) — what an authenticated user can read; that posture assumes a real auth mode is enabled, which is this decision.
* [Enable security](/configuration-and-deployment/enable-security.md) — the operator guide to choosing and configuring an authentication mode.
