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

Auth is one of four modes set by auth.type (DISABLED, LOGIN_FORM, OAUTH2, LDAP). It ships DISABLED, which is fully open — enable a real mode for any networked deployment.

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:

  • DISABLEDanyExchange().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 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-34auth: 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

Last updated