Audit logs
Every meaningful event Febasi Auth records, what's in each row, and how to query it.
Every authentication and authorization event is appended to the auth_logs
table. Logs are tenant-scoped, immutable, and exposed through the
GET /auth/logs endpoint to anyone holding the auth:logs:read permission.
What gets logged
| Action | When it fires |
|---|---|
LOGIN | Every POST /login — success or failure. |
LOGOUT | Every POST /logout. |
REFRESH_TOKEN | Every POST /refresh — success or failure. |
REGISTER | Every POST /register — including failures. |
USER_DELETE | When a user is deleted via /users/:id. |
ROLE_ASSIGN | POST /roles/assign. |
ROLE_REMOVE | POST /roles/remove. |
PERMISSION_GRANT | POST /permissions/grant. |
PERMISSION_REVOKE | POST /permissions/revoke. |
IP_DENIED | A request was rejected because the source IP is not in the tenant or per-key allowlist. See IP allowlist. |
IP_ALLOWLIST_FORCE_UPDATE | An allowlist update used ?force=true to bypass the self-lockout check. High-severity — review periodically. |
Failures are logged with success = false and errorMessage populated, so
you can spot brute-force attempts, mistyped tokens, and forbidden actions
all in the same query.
Row shape
| Field | Type | Notes |
|---|---|---|
id | UUID | Primary key. |
tenant_id | UUID | Always present. Scopes the row. |
user_id | UUID (nullable) | Null for failed logins where the user was unknown. |
action | enum | One of the action codes above. |
success | boolean | true for successful events. |
error_message | text (nullable) | Reason on failures. |
ip_address | string | Source IP from X-Forwarded-For or socket. |
user_agent | string | Client User-Agent if present. |
created_at | timestamp | Insertion time. Indexed for range queries. |
Querying the log
GET /api/v1/auth/logs?action=LOGIN&success=false&limit=50&offset=0
Authorization: Bearer <jwt-with-auth:logs:read>Supported filters: userId, action, success, startDate, endDate
(both as ISO 8601 datetimes), plus limit (1–100, default 50) and offset
(default 0).
Common query patterns:
GET /api/v1/auth/logs?action=LOGIN&success=false&startDate=2026-05-21T00:00:00Z&endDate=2026-05-22T00:00:00ZWire alerts on the count of these. A cluster of failures from a single IP in a 5-minute window is a brute-force fingerprint.
GET /api/v1/auth/logs?action=PERMISSION_GRANT&userId={id}Useful for compliance reviews — "show me everything that has been granted to this user."
GET /api/v1/auth/logs?userId={id}&startDate=2026-05-15T00:00:00Z&endDate=2026-05-22T00:00:00Z&limit=200Cross-reference with /tenants/me/metrics/security for IPs flagged as
suspicious — then drill in here by userId for the forensic timeline.
Retention
Audit rows are never overwritten. They are kept indefinitely on the production host; a formal retention policy is owned by the operations team. Plan for "rows live forever".
What's not in audit logs
- Read endpoints —
GET /users,GET /roles, etc. are not audited by default. If your integration needs read auditing, contact your Febasi account to discuss enabling it for your tenant. - JWT secret rotation — captured in tenant metadata, not in
auth_logs. - Outbound webhook deliveries — Febasi Auth does not emit webhooks. The audit log is the closest thing to an event stream.
Pairing with security metrics
For real-time signals (last 5 min / last 24h aggregates), use:
GET /api/v1/tenants/me/metrics/securityFor a forensic timeline of a specific user / IP / action, use the audit log. The two are intentionally separate: one is fast and aggregate, the other is authoritative and granular.