Authsome

Chronicle Bridge

Audit logging integration that records every auth event to a Chronicle backend.

The Chronicle bridge connects Authsome to the Chronicle audit logging extension. When configured, every authentication event — sign-in, sign-up, MFA enrollment, password reset, session revocation, and more — is recorded to Chronicle's append-only audit trail with full actor, tenant, outcome, and severity metadata.

Interface

The bridge.Chronicle interface is defined in github.com/xraph/authsome/bridge:

type Chronicle interface {
    Record(ctx context.Context, event *AuditEvent) error
}

type AuditEvent struct {
    Action     string            `json:"action"`
    Resource   string            `json:"resource"`
    ResourceID string            `json:"resource_id,omitempty"`
    ActorID    string            `json:"actor_id,omitempty"`
    Tenant     string            `json:"tenant,omitempty"`
    Outcome    string            `json:"outcome"`
    Severity   string            `json:"severity"`
    Metadata   map[string]string `json:"metadata,omitempty"`
    Reason     string            `json:"reason,omitempty"`
}

Severity constants

const (
    SeverityInfo     = "info"
    SeverityWarning  = "warning"
    SeverityCritical = "critical"
)

Outcome constants

const (
    OutcomeSuccess = "success"
    OutcomeFailure = "failure"
)

Setup with the Chronicle adapter

import (
    "github.com/xraph/authsome"
    "github.com/xraph/authsome/bridge/chronicleadapter"
    "github.com/xraph/chronicle"
)

// Build the Chronicle engine (see Chronicle docs for full setup).
chronicleEng, err := chronicle.New(
    chronicle.WithStore(chronicleStore),
    chronicle.WithAppID("myapp"),
)
if err != nil {
    log.Fatal(err)
}

// Wrap in the Authsome adapter.
chronicleBridge := chronicleadapter.New(chronicleEng)

// Register with Authsome.
eng, err := authsome.New(
    authsome.WithStore(pgStore),
    authsome.WithPlugin(password.New()),
    authsome.WithChronicle(chronicleBridge),
)

Events recorded

The Chronicle bridge receives events for every Authsome operation. The table below lists the most important events:

ActionResourceSeverityTrigger
auth.signupuserinfoNew user registered
auth.signinsessioninfoSuccessful sign-in
auth.signin.failedsessionwarningFailed sign-in attempt
auth.signoutsessioninfoUser signed out
auth.password.resetaccountinfoPassword reset completed
auth.password.changeaccountinfoPassword changed
auth.mfa.enrolledmfainfoMFA method enrolled
auth.mfa.verifiedmfainfoMFA challenge passed
auth.mfa.failedmfawarningMFA challenge failed
auth.passkey.registeredpasskeyinfoPasskey registered
auth.passkey.authenticatedpasskeyinfoPasskey login
auth.social.signinsessioninfoSocial OAuth sign-in
auth.sso.signinsessioninfoSSO SAML/OIDC sign-in
session.revokedsessioninfoSession explicitly revoked
device.trusteddeviceinfoDevice marked as trusted
user.banneduserwarningUser account banned
account.lockedaccountcriticalAccount locked out after repeated failures
apikey.createdapikeyinfoAPI key created
apikey.revokedapikeywarningAPI key revoked

Standalone development stub

During development, use the built-in SlogChronicle stub that logs audit events to slog without requiring a Chronicle instance:

import (
    "log/slog"
    "github.com/xraph/authsome/bridge"
)

eng, err := authsome.New(
    authsome.WithStore(memory.New()),
    authsome.WithChronicle(bridge.NewSlogChronicle(slog.Default())),
)

The stub logs a structured message for each event:

INFO authsome audit action=auth.signin resource=session resource_id=ases_01j... actor_id=ausr_01j... tenant=aorg_01j... outcome=success severity=info

Custom Chronicle implementation

You can implement bridge.Chronicle directly for any audit backend:

type BigQueryChronicle struct {
    client    *bigquery.Client
    tableRef  *bigquery.Table
}

func (c *BigQueryChronicle) Record(ctx context.Context, event *bridge.AuditEvent) error {
    row := map[string]bigquery.Value{
        "action":      event.Action,
        "resource":    event.Resource,
        "resource_id": event.ResourceID,
        "actor_id":    event.ActorID,
        "tenant":      event.Tenant,
        "outcome":     event.Outcome,
        "severity":    event.Severity,
        "timestamp":   time.Now().UTC(),
    }
    inserter := c.tableRef.Inserter()
    return inserter.Put(ctx, row)
}

// Register:
eng, err := authsome.New(
    authsome.WithStore(pgStore),
    authsome.WithChronicle(&BigQueryChronicle{...}),
)

Asynchronous recording

The Chronicle bridge's Record method is called synchronously within the auth operation. If your Chronicle backend has high latency (remote API calls, slow storage), you can wrap it in an async adapter:

type AsyncChronicle struct {
    inner   bridge.Chronicle
    queue   chan *bridge.AuditEvent
    logger  *slog.Logger
}

func NewAsyncChronicle(inner bridge.Chronicle) *AsyncChronicle {
    ac := &AsyncChronicle{
        inner:  inner,
        queue:  make(chan *bridge.AuditEvent, 1000),
        logger: slog.Default(),
    }
    go ac.worker()
    return ac
}

func (ac *AsyncChronicle) Record(ctx context.Context, event *bridge.AuditEvent) error {
    select {
    case ac.queue <- event:
        return nil
    default:
        // Queue full — log and drop rather than blocking the auth path.
        ac.logger.Warn("chronicle queue full, dropping event", "action", event.Action)
        return nil
    }
}

func (ac *AsyncChronicle) worker() {
    for event := range ac.queue {
        if err := ac.inner.Record(context.Background(), event); err != nil {
            ac.logger.Error("chronicle record failed", "error", err)
        }
    }
}

Async recording means audit events may be lost if the process crashes before the queue drains. Use this pattern only when audit event latency directly impacts user-facing authentication performance. For compliance-critical scenarios, prefer synchronous recording or Chronicle's built-in batching support.

On this page