Audit Trail
Append-only security event log with IP and user agent recording, time-range queries, cursor pagination, and Chronicle bridge integration.
Authsome records every significant authentication and authorization event to an append-only audit trail. The audit trail is the authoritative record for security investigations, compliance reporting, and user activity monitoring. Events are immutable -- there is no update or delete operation on the audit store.
The SecurityEvent model
import "github.com/xraph/authsome/audit"
type SecurityEvent struct {
ID id.ID `json:"id"` // asec_ prefix
AppID string `json:"app_id"`
EnvID string `json:"env_id"`
UserID *id.ID `json:"user_id,omitempty"`
SessionID *id.ID `json:"session_id,omitempty"`
OrgID *id.ID `json:"org_id,omitempty"`
Action string `json:"action"` // e.g. "auth.signin"
Outcome string `json:"outcome"` // "success" or "failure"
IP string `json:"ip,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
Country string `json:"country,omitempty"`
City string `json:"city,omitempty"`
DeviceType string `json:"device_type,omitempty"`
Reason string `json:"reason,omitempty"` // on failure
Metadata map[string]any `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at"`
}| Field | Description |
|---|---|
Action | The event type string, e.g. "auth.signin", "auth.mfa.verified", "passkey.registered" |
Outcome | "success" or "failure" |
IP | Client IP address extracted from the request context |
UserAgent | Full user agent string from the request |
Country / City | Geo-location data if IP geolocation is configured |
DeviceType | Parsed device type: "desktop", "mobile", "tablet", "bot" |
Reason | Human-readable failure reason, e.g. "invalid_password", "token_expired" |
Metadata | Additional context key-value pairs (e.g. MFA method, SSO provider name) |
Event types
Authsome records events across all authentication methods:
| Action | Category | Description |
|---|---|---|
auth.signin | Auth | Successful sign-in |
auth.signin.failed | Auth | Failed sign-in attempt |
auth.signout | Auth | User signed out |
auth.password.reset | Auth | Password was reset |
auth.mfa.enabled | MFA | MFA was enabled for the account |
auth.mfa.disabled | MFA | MFA was disabled for the account |
auth.mfa.enrolled | MFA | MFA method was enrolled (e.g., TOTP added) |
auth.mfa.verified | MFA | MFA challenge was verified successfully |
auth.mfa.challenged | MFA | MFA challenge was initiated |
passkey.registered | Passkey | A new passkey credential was registered |
passkey.authenticated | Passkey | Authentication via passkey succeeded |
passkey.deleted | Passkey | A passkey credential was removed |
social.signin | Social | OAuth social sign-in |
social.signup | Social | Account created via social OAuth |
sso.signin | SSO | Enterprise SSO sign-in |
sso.signup | SSO | Account created via SSO |
session.created | Session | A new session was created |
session.revoked | Session | A session was explicitly revoked |
apikey.created | API Key | A new API key was created |
apikey.revoked | API Key | An API key was revoked |
org.member.invited | Org | A user was invited to an organization |
org.member.joined | Org | A user accepted an org invitation |
org.member.removed | Org | A user was removed from an organization |
The SecurityEventStore interface
type SecurityEventStore interface {
// Record appends a new security event. Errors are logged but do not fail the auth operation.
Record(ctx context.Context, event *SecurityEvent) error
// List returns events for an app within a time range, with cursor pagination.
List(ctx context.Context, opts ListEventsOpts) ([]*SecurityEvent, string, error)
// ListByUser returns events for a specific user.
ListByUser(ctx context.Context, userID id.ID, opts ListEventsOpts) ([]*SecurityEvent, string, error)
// ListBySession returns events for a specific session.
ListBySession(ctx context.Context, sessionID id.ID, opts ListEventsOpts) ([]*SecurityEvent, string, error)
}ListEventsOpts
type ListEventsOpts struct {
AppID string
EnvID string
Limit int // default 50, max 500
Cursor string // opaque cursor from previous NextCursor
Since *time.Time // return events after this time
Until *time.Time // return events before this time
Actions []string // filter by action strings
Outcomes []string // filter by "success" or "failure"
IPFilter string // exact IP match
}The list methods return (events, nextCursor, error). Pass nextCursor as the Cursor in the next call to get the next page. An empty nextCursor means you have reached the last page.
Querying the audit trail
List recent events for the app
import "github.com/xraph/authsome/audit"
since := time.Now().Add(-24 * time.Hour)
events, nextCursor, err := auth.Audit().List(ctx, audit.ListEventsOpts{
Since: &since,
Limit: 100,
})
if err != nil {
return err
}
for _, e := range events {
fmt.Printf(" [%s] %s %s ip=%s user=%s\n",
e.CreatedAt.Format(time.RFC3339),
e.Action,
e.Outcome,
e.IP,
e.UserID,
)
}List events for a specific user
// Last 30 days of events for a user.
since := time.Now().Add(-30 * 24 * time.Hour)
events, nextCursor, err := auth.Audit().ListByUser(ctx, userID, audit.ListEventsOpts{
Since: &since,
Limit: 50,
})Filter by action and outcome
// All failed sign-ins in the last 7 days.
since := time.Now().Add(-7 * 24 * time.Hour)
events, _, err := auth.Audit().List(ctx, audit.ListEventsOpts{
Since: &since,
Actions: []string{"auth.signin", "auth.signin.failed"},
Outcomes: []string{"failure"},
Limit: 200,
})Paginating through results
opts := audit.ListEventsOpts{
Since: &since,
Limit: 100,
}
for {
events, nextCursor, err := auth.Audit().List(ctx, opts)
if err != nil {
return err
}
process(events)
if nextCursor == "" {
break
}
opts.Cursor = nextCursor
}Chronicle bridge integration
Chronicle is the Forge-ecosystem's dedicated append-only event log service. When Chronicle is configured, Authsome forwards every security event to Chronicle in addition to the local audit store, giving you centralized cross-service audit logging.
import (
"github.com/xraph/authsome"
"github.com/xraph/authsome/audit/chronicle"
)
auth := authsome.New(
authsome.WithAuditBridge(chronicle.NewBridge(
chronicle.WithEndpoint("https://chronicle.internal:8080"),
chronicle.WithAPIKey(os.Getenv("CHRONICLE_API_KEY")),
chronicle.WithAsync(true), // non-blocking delivery
chronicle.WithBufferSize(1000), // in-flight event buffer
)),
)| Option | Default | Description |
|---|---|---|
WithEndpoint | required | Chronicle service endpoint |
WithAPIKey | required | API key for Chronicle authentication |
WithAsync | false | When true, events are delivered asynchronously -- sign-in is not blocked by delivery failures |
WithBufferSize | 500 | Size of the async delivery buffer. Events are dropped (and logged) if the buffer is full. |
WithActions | all | When set, only the specified action strings are forwarded to Chronicle |
With WithAsync(true), a temporary Chronicle outage will not affect authentication. Events in the buffer are delivered when Chronicle recovers. Events that overflow the buffer are written to the application log with level=warn.
HTTP API endpoints
| Method | Path | Description |
|---|---|---|
GET | /admin/audit | List all security events with filters |
GET | /admin/audit/users/:user_id | List security events for a specific user |
GET | /admin/audit/sessions/:session_id | List security events for a specific session |
GET | /users/me/security-events | User-facing: list the current user's own security events |
Rate Limiting
Per-endpoint rate limits with configurable windows, memory and noop built-in limiters, and a pluggable interface for Redis or custom backends.
IP Access Control
IP whitelist and blacklist configuration for application-level access control, and session IP binding for session hijacking prevention.