ums

ADR-0072: Dynamic Auth Method Resolution — From Configuration, Not Code

UMS-specific: No Evolith upstream. Dynamic auth method resolution is part of UMS’s authorization graph engine — a product-domain decision not applicable to generic satellite architecture.

Status: Accepted Date: 2026-05-31 Decision Owner: Architecture Related:


Context

UMS supports both Local (BCrypt) and external IDP authentication per tenant. Previously, the login endpoint used only BCrypt unconditionally — a hardcoded strategy that did not reflect the tenant’s configured authentication method. The portal management flow and the external API flow now need different behavior even when they belong to the same tenant.

The tenant’s auth method is controlled by AUTH_USE_EXTERNAL_IDP (a parameter in the ParameterCatalog, tenant-scoped). When true, the tenant uses external Identity Providers (Azure AD, Okta, etc.); when false, local BCrypt is used. This configuration change must take effect without deploying new code.


Decision

1. IAuthMethodResolver Port

IAuthMethodResolver.ResolveAsync(tenantId, scope) reads AUTH_USE_EXTERNAL_IDP from IConfigurationProvider (already in memory) — zero DB queries per request. Returns:

For AuthAccessScope.PortalManagement, the resolver always returns AuthMethod.Local() and does not require the tenant IDP configuration.

The resolver is a pure application service — no infrastructure dependencies.

2. Strategy Pattern via Domain Ports

ILocalAuthStrategy  — validates BCrypt credentials (Application layer)
IIdpAuthStrategy    — dispatches to correct IIdpAuthAdapter by strategy name
IIdpAuthAdapter     — ACL port per provider type (Infrastructure layer)

The IIdpAuthStrategy uses Shell.Factory to resolve the correct IIdpAuthAdapter (same pattern as IdpResolutionStrategyFactorySetup). New IDP adapters are added by registering a new implementation — no changes to the auth flow.

3. Development Stub IDP Adapter

StubIdpAuthAdapter accepts credentials starting with MOCK- prefix and returns a valid ExternalIdentity. It is registered in non-Production environments only, allowing end-to-end testing of the IDP flow without a real external provider.

4. Auth Error Codes

Code Meaning
AUTH_001 Validation error (missing fields)
AUTH_002 Tenant not found
AUTH_003 Tenant not active
AUTH_004 IDP user has no UMS account
AUTH_005 User account not active
AUTH_006 Invalid credentials (Local)
AUTH_011 IDP mode configured but no active provider
AUTH_012 No IDP adapter registered for strategy

Rationale

Why read from IConfigurationProvider, not the DB

IConfigurationProvider loads all configuration values at startup and caches them in memory. Reading from it is a dictionary lookup — no DB roundtrip. When a tenant’s auth mode is changed (e.g., via the IdpPanel toggle), the configuration is updated in the DB and the provider is refreshed. The change takes effect on the next login without restarting the service.

Why not hardcode BCrypt as the default

Hardcoding creates implicit coupling between the auth flow and a specific implementation. Any new auth strategy (SAML2, OAuth2, custom) would require modifying the command handler. The strategy pattern allows new methods to be added by registering a new adapter.


Consequences

Positive

Trade-offs


Implementation

Component Location
IAuthMethodResolver port Ums.Domain/Identity/Auth/IAuthMethodResolver.cs
AuthMethodResolverService Ums.Application/Identity/Auth/AuthMethodResolverService.cs
ILocalAuthStrategy port Ums.Domain/Identity/Auth/ILocalAuthStrategy.cs
LocalAuthStrategyService Ums.Application/Identity/Auth/LocalAuthStrategyService.cs
IIdpAuthStrategy port Ums.Domain/Identity/Auth/IIdpAuthStrategy.cs
IIdpAuthAdapter ACL port Ums.Domain/Identity/Auth/IIdpAuthAdapter.cs
IdpAuthStrategyDispatcher Ums.Infrastructure/Identity/Auth/IdpAuthStrategyDispatcher.cs
StubIdpAuthAdapter Ums.Infrastructure/Identity/Auth/StubIdpAuthAdapter.cs
IdpAuthAdapterFactorySetup Ums.Infrastructure/Identity/Auth/IdpAuthAdapterFactorySetup.cs

ADR Registry