5.1 KiB
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: returncode + 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:
statuscodesafeMessagelogMessagedebugFields
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_parseinvalid_client_assertion_signatureinvalid_client_assertion_iss_subinvalid_client_assertion_expiredinvalid_client_assertion_not_beforeinvalid_client_assertion_iat_futureinvalid_client_assertion_audienceinvalid_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_codeclient_idpathreq_idif present in context/log pipeline
Message examples:
headless password login client assertion failedheadless password login credential authentication failed
Debug log level
Add diagnostic fields that are useful for root cause analysis:
expected_audiencesreceived_audiencesreceived_kidjwks_refreshedclaim_issuerclaim_subjectclaim_expires_atclaim_not_beforeclaim_issued_atlogin_challenge_prefix
The login_challenge should be truncated before logging.
Implementation Shape
Inside backend/internal/handler/auth_handler.go:
- Add a small internal type for classified headless assertion errors.
- Make
validateHeadlessClientAssertionClaimsreturn structured reasons instead of generic strings. - Make
verifyHeadlessClientAssertionreturn a classified failure that the handler can both:- convert to a safe HTTP response
- log with optional debug fields
- Add a small helper that checks whether debug logging is enabled for the current default
sloglogger. - 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_assertioncode 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