1
0
forked from baron/baron-sso
Files
baron-sso/docs/superpowers/specs/2026-04-01-headless-login-debug-design.md
2026-04-01 20:32:09 +09:00

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: 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

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