# Headless Login Debug and Error Classification Design ## Scope This design covers issue `#502` only. - Target endpoint: `POST /api/v1/auth/headless/password/login` - Goal: classify 401 failure reasons for headless password login, return a safe response to RP clients, and emit richer diagnostics only when debug logging is enabled - Non-goal: introduce a shared auth-wide error/observability layer across all handlers. That follow-up work is tracked separately in issue `#503`. ## Problem Current behavior makes production debugging slow. - Multiple assertion failures collapse into `invalid_client_assertion` - The handler often returns early without emitting a reason-specific log line - RP clients cannot distinguish safe failure categories such as audience mismatch vs signature mismatch - Server operators cannot quickly tell whether the failure came from client assertion validation or user credential validation ## Constraints - Response policy must follow the agreed level `2`: return `code + short safe message` - Debug logs may include level `3`-style diagnostics, but only when the logger is configured for debug level - Sensitive values must never be logged: raw `client_assertion`, password, session token, cookies - Existing successful headless password login flow must remain unchanged - Existing policy requires failing tests first ## Recommended Approach Introduce a small headless-password-specific failure classification layer inside `auth_handler.go`. The classification layer should map each failure into: - `status` - `code` - `safeMessage` - `logMessage` - `debugFields` This stays local to the current handler so the change remains small, but the structure should be reusable enough to extract later in issue `#503`. ## Failure Categories ### Client assertion failures - `invalid_client_assertion_parse` - `invalid_client_assertion_signature` - `invalid_client_assertion_iss_sub` - `invalid_client_assertion_expired` - `invalid_client_assertion_not_before` - `invalid_client_assertion_iat_future` - `invalid_client_assertion_audience` - `invalid_client_assertion_jwks_load` ### Credential failure - `password_or_email_mismatch` ## Response Contract Responses should expose only safe details. Examples: - `{"code":"invalid_client_assertion_audience","error":"Client assertion audience mismatch"}` - `{"code":"invalid_client_assertion_signature","error":"Client assertion signature verification failed"}` - `{"code":"password_or_email_mismatch","error":"Invalid credentials"}` The response must not echo: - expected vs received audience values - full claim payload - kid mismatch internals beyond the safe message - raw token values ## Logging Strategy ### Normal log level Emit one structured warning/error log per classified failure with: - `reason_code` - `client_id` - `path` - `req_id` if present in context/log pipeline Message examples: - `headless password login client assertion failed` - `headless password login credential authentication failed` ### Debug log level Add diagnostic fields that are useful for root cause analysis: - `expected_audiences` - `received_audiences` - `received_kid` - `jwks_refreshed` - `claim_issuer` - `claim_subject` - `claim_expires_at` - `claim_not_before` - `claim_issued_at` - `login_challenge_prefix` The `login_challenge` should be truncated before logging. ## Implementation Shape Inside `backend/internal/handler/auth_handler.go`: 1. Add a small internal type for classified headless assertion errors. 2. Make `validateHeadlessClientAssertionClaims` return structured reasons instead of generic strings. 3. Make `verifyHeadlessClientAssertion` return a classified failure that the handler can both: - convert to a safe HTTP response - log with optional debug fields 4. Add a small helper that checks whether debug logging is enabled for the current default `slog` logger. 5. Log credential authentication failures with their reason code as well. ## Testing Add failing tests first in `backend/internal/handler/auth_handler_login_test.go`. Required tests: - audience mismatch returns `401 + invalid_client_assertion_audience` - signature mismatch returns `401 + invalid_client_assertion_signature` - iss/sub mismatch returns `401 + invalid_client_assertion_iss_sub` - expired assertion returns `401 + invalid_client_assertion_expired` - invalid credentials still returns `401 + password_or_email_mismatch` - debug logger captures diagnostic fields for assertion audience mismatch - non-debug logger does not emit debug-only fields ## Risks - Over-classifying too much detail into RP responses could create unnecessary information disclosure - Logging helper implementation must not assume a specific handler type beyond standard `slog` - Existing tests may assume the older generic `invalid_client_assertion` code and will need intentional updates ## Success Criteria - Operators can distinguish signature, claims, and credential failures from logs - RP clients get safe but actionable reason codes - Debug mode includes additional diagnostics without leaking secrets - Existing success path and previously covered headless login behavior remain green