Compass: Authentication Flow Engine

Compass records each authentication attempt as a flow: a sequence of named steps, each with its own outcome, duration, and error details. It is how you find out where a login actually went wrong.

Why

From outside, authentication is one request and one token. Inside, it is redirect validation, credential hashing, an MFA challenge, maybe an external IdP round trip, session creation, and token signing. When it fails, 401 tells you nothing about which of those broke.

With Compass you get this instead:

Flow 01914b3c-... for client my-frontend via authorization_code: ✓ authorize (12ms) → ✓ credential_validation (85ms) → ✗ mfa_challenge (0ms, error: invalid_otp) → Flow failed at 97ms

How it works

With Compass on, every authentication request opens a CompassFlow. As the request moves through credential validation, the MFA challenge, token exchange, and finalization, each step is written as a CompassFlowStep with its own status and duration.

graph TD
    A[Flow Started] --> B[Authorize]
    B --> C[Credential Validation]
    C --> D{MFA Required?}
    D -->|Yes| E[MFA Challenge]
    D -->|No| F[Token Exchange]
    E --> F
    F --> G[Finalize]
    G --> H[Flow Completed]

Flow lifecycle

Flow Created

When an authentication request arrives, Compass creates a CompassFlow with the realm, client, grant type, IP address, and user agent. The flow status is pending.

Steps Recorded

As the authentication progresses, each step (authorize, credential validation, MFA, etc.) is recorded with its outcome, duration, and any error details. Steps are persisted asynchronously.

User Identified

After successful credential validation, the user_id is attached to the flow. This links the flow to a specific user for later querying.

Flow Completed

When authentication finishes (success or failure), the flow is marked as completed with a final status, completion timestamp, and total duration in milliseconds.

Flow structure

A CompassFlow holds the whole attempt:

FieldTypeDescription
idUUIDv7Unique flow identifier (time-ordered for efficient querying)
realm_idUUIDRealm context
client_idStringClient ID that initiated the authentication
user_idUUID?Authenticated user (set after credential validation succeeds)
grant_typeStringOAuth2 grant type (authorization_code, password, client_credentials, refresh_token)
statusFlowStatusOverall outcome: pending, success, failure, expired
ip_addressString?Source IP address
user_agentString?Client user agent
started_atDateTimeWhen the flow began
completed_atDateTime?When the flow finished (null while pending)
duration_msi64?Total flow duration in milliseconds
stepsVec<FlowStep>Ordered list of step records

Flow status values

StatusMeaning
pendingFlow is in progress, steps are still being recorded
successAuthentication completed successfully, tokens were issued
failureAuthentication failed at one of the steps
expiredThe auth session expired before completion (user took too long)

Flow steps

Each step records:

FieldTypeDescription
idUUIDv7Unique step identifier
flow_idUUIDParent flow reference
step_nameFlowStepNameOne of the nine step types
statusStepStatussuccess, failure, or skipped
duration_msi64?Step execution time in milliseconds
error_codeString?Machine-readable error code (on failure)
error_messageString?Human-readable error description (on failure)
started_atDateTimeWhen the step began executing

Step types

StepWhen It ExecutesTypical Duration
authorizeOAuth2 authorization request validation, checks redirect URI, scope, response type, CSRF state<5ms
credential_validationUsername lookup + password hash verification (Argon2)50-200ms
mfa_challengeTOTP code validation or WebAuthn assertion verification5-50ms
token_exchangeAuthorization code → token exchange (code lookup + token generation)10-30ms
idp_redirectBuilding and recording the redirect to an external identity provider<5ms
idp_callbackProcessing the callback from an external IdP (token exchange + user lookup)100-500ms
finalizeSession creation, SeaWatch event emission, and cleanup5-15ms
saml_authn_requestParsing an incoming SAML AuthnRequest and resolving the service provider<5ms
saml_assertionBuilding and signing the SAML assertion sent back to the service provider5-20ms

Step status values

StatusMeaning
successStep completed successfully
failureStep failed, check error_code and error_message
skippedStep was not applicable (e.g., mfa_challenge skipped when no MFA is configured)

Configuration

SettingDefaultDescription
compass_enabledtrueEnable/disable flow recording per realm

Turn it off and authentication behaves identically, minus the recording: no flows, no steps, no database writes. Worth doing on realms where you do not need step-level observability.

Off costs nothing

With compass_enabled set to false, the FlowRecorder short-circuits on entry. No channel is used, no row is written, no flow object is allocated.