Audit Logs
Query and display authentication audit history in your app. Audit logging is enabled by default --- this guide shows how to configure it, query events from the backend, and display login activity in your frontend.
The code in this guide is taken directly from the example apps. If you get stuck, clone and run them:
Prerequisites
- A working auth setup (Quick Start)
- Audit logging enabled (default --- no configuration needed)
Step 1: Configure
Audit logging is enabled by default. No configuration is required for most setups:
{
auditLogs: {
enabled: true, // default
},
}
For high-throughput environments, enable fire-and-forget mode to avoid awaiting audit writes on the request path:
{
auditLogs: {
enabled: true,
fireAndForget: true, // Don't await audit writes
},
}
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable/disable audit logging |
fireAndForget | boolean | false | Don't await audit writes on request path |
Disabling audit logs reduces security observability. Keep audit logging enabled in production systems.
Step 2: Query Audit History (Backend)
Inject AuthAuditService to query audit records from your backend code:
- NestJS
- Express / Fastify
import { Controller, Get, Query } from '@nestjs/common';
import { AuthAuditService } from '@nauth-toolkit/nestjs';
import { AuthAuditEventType } from '@nauth-toolkit/core';
@Controller('admin/audit')
export class AuditController {
constructor(private readonly auditService: AuthAuditService) {}
@Get('user-history')
async getUserHistory(
@Query('sub') sub: string,
@Query('page') page = 1,
@Query('limit') limit = 50,
) {
return this.auditService.getUserAuthHistory({
sub,
page,
limit,
});
}
@Get('suspicious')
async getSuspicious() {
return this.auditService.getSuspiciousActivity({
limit: 100,
});
}
@Get('by-type')
async getByType(
@Query('eventType') eventType: AuthAuditEventType,
@Query('page') page = 1,
) {
return this.auditService.getEventsByType({
eventType,
page,
limit: 50,
});
}
}
import { AuthAuditService, AuthAuditEventType } from '@nauth-toolkit/core';
// auditService is available from NAuth.create() result
export function registerAuditRoutes(
app: Express,
auditService: AuthAuditService,
) {
app.get('/admin/audit/user-history', async (req, res) => {
const { sub, page = 1, limit = 50 } = req.query;
const result = await auditService.getUserAuthHistory({
sub: sub as string,
page: Number(page),
limit: Number(limit),
});
res.json(result);
});
app.get('/admin/audit/suspicious', async (req, res) => {
const result = await auditService.getSuspiciousActivity({
limit: 100,
});
res.json(result);
});
}
Query Methods
| Method | Purpose | Parameters |
|---|---|---|
getUserAuthHistory() | Full history for one user | sub, page, limit, eventTypes[], eventStatus[], startDate, endDate |
getEventsByType() | All events of a specific type | eventType, page, limit, startDate, endDate |
getSuspiciousActivity() | Suspicious/risky events | sub? (optional), limit |
getRiskAssessmentHistory() | Adaptive MFA risk events | sub, limit |
Filtering Examples
// Login failures in the last 24 hours
const failures = await auditService.getUserAuthHistory({
sub: 'user-uuid',
eventTypes: [AuthAuditEventType.LOGIN_FAILED, AuthAuditEventType.LOGIN_BLOCKED],
eventStatus: ['FAILURE'],
startDate: new Date(Date.now() - 24 * 60 * 60 * 1000),
page: 1,
limit: 50,
});
// All suspicious activity across all users
const suspicious = await auditService.getSuspiciousActivity({
limit: 100,
});
// All social logins this month
const socialLogins = await auditService.getEventsByType({
eventType: AuthAuditEventType.SOCIAL_LOGIN,
startDate: new Date('2025-02-01'),
page: 1,
limit: 100,
});
Response Format
All query methods return paginated responses:
{
"data": [
{
"id": 1042,
"userId": 15,
"eventType": "LOGIN_SUCCESS",
"eventStatus": "SUCCESS",
"ipAddress": "203.0.113.42",
"ipCountry": "US",
"ipCity": "San Francisco",
"platform": "macOS",
"browser": "Chrome",
"deviceName": "Chrome on MacBook",
"deviceType": "desktop",
"authMethod": "password",
"performedBy": "550e8400-e29b-41d4-a716-446655440000",
"metadata": null,
"createdAt": "2025-02-22T14:30:00.000Z"
},
{
"id": 1041,
"userId": 15,
"eventType": "MFA_VERIFICATION_SUCCESS",
"eventStatus": "SUCCESS",
"ipAddress": "203.0.113.42",
"ipCountry": "US",
"ipCity": "San Francisco",
"platform": "macOS",
"browser": "Chrome",
"authMethod": "totp",
"metadata": {
"deviceType": "totp",
"deviceName": "Authenticator App"
},
"createdAt": "2025-02-22T14:29:55.000Z"
},
{
"id": 1038,
"userId": 15,
"eventType": "LOGIN_FAILED",
"eventStatus": "FAILURE",
"ipAddress": "198.51.100.23",
"ipCountry": "DE",
"ipCity": "Berlin",
"platform": "Windows",
"browser": "Firefox",
"deviceType": "desktop",
"authMethod": "password",
"reason": "Invalid credentials",
"metadata": null,
"createdAt": "2025-02-22T10:15:00.000Z"
}
],
"total": 247,
"page": 1,
"limit": 50,
"totalPages": 5
}
Step 3: Display Audit History (Frontend)
Current User's History
Use the client SDK to fetch the authenticated user's own audit history:
- Angular
- React / JS
import { Component, OnInit } from '@angular/core';
import { AuthService } from '@nauth-toolkit/client-angular';
import { AuditHistoryResponse, AuthAuditEvent } from '@nauth-toolkit/client';
@Component({
selector: 'app-audit-history',
template: `
<h2>Login Activity</h2>
<table>
<thead>
<tr>
<th>Event</th>
<th>Status</th>
<th>Location</th>
<th>Device</th>
<th>Date</th>
</tr>
</thead>
<tbody>
@for (event of events; track event.id) {
<tr [class.suspicious]="event.eventStatus === 'SUSPICIOUS'">
<td>{{ formatEventType(event.eventType) }}</td>
<td>{{ event.eventStatus }}</td>
<td>{{ event.ipCity }}, {{ event.ipCountry }}</td>
<td>{{ event.deviceName || event.browser }}</td>
<td>{{ event.createdAt | date:'medium' }}</td>
</tr>
}
</tbody>
</table>
`,
})
export class AuditHistoryComponent implements OnInit {
events: AuthAuditEvent[] = [];
constructor(private auth: AuthService) {}
async ngOnInit() {
const response = await this.auth.getAuditHistory({
page: 1,
limit: 20,
});
this.events = response.data;
}
formatEventType(type: string): string {
return type.replace(/_/g, ' ').toLowerCase()
.replace(/^\w/, c => c.toUpperCase());
}
}
import { useEffect, useState } from 'react';
import { NAuthClient, AuthAuditEvent } from '@nauth-toolkit/client';
export function AuditHistory({ client }: { client: NAuthClient }) {
const [events, setEvents] = useState<AuthAuditEvent[]>([]);
useEffect(() => {
client.getAuditHistory({ page: 1, limit: 20 })
.then(res => setEvents(res.data));
}, [client]);
return (
<table>
<thead>
<tr>
<th>Event</th>
<th>Status</th>
<th>Location</th>
<th>Device</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{events.map(event => (
<tr key={event.id}>
<td>{event.eventType.replace(/_/g, ' ')}</td>
<td>{event.eventStatus}</td>
<td>{event.ipCity}, {event.ipCountry}</td>
<td>{event.deviceName || event.browser}</td>
<td>{new Date(event.createdAt).toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
);
}
Admin: Any User's History
Use the admin operations client to query any user's audit history:
- Angular
- React / JS
const history = await this.auth.admin?.getAuditHistory({
sub: 'user-uuid',
page: 1,
limit: 50,
eventType: 'LOGIN_FAILED',
});
const history = await client.admin.getAuditHistory({
sub: 'user-uuid',
page: 1,
limit: 50,
eventType: 'LOGIN_FAILED',
});
Troubleshooting
No audit records being created:
- Verify
auditLogs.enabledis not set tofalse - Check server logs for audit recording errors
- Verify the database migration has run (audit table exists)
Missing geolocation data:
- Configure Geolocation ---
ipCountry,ipCity, and coordinates require MaxMind - Without geolocation, only
ipAddressis captured
Audit endpoint returns 404:
- Verify audit logging is enabled in your config
- Check that your backend exposes the audit endpoints (NestJS auto-registers them)
High database growth:
- Enable
fireAndForget: truefor write performance - Implement retention policies --- periodically delete old records
- Consider archiving to cold storage for compliance
What's Next
- Audit Logs --- How audit logging works (event types catalog, data model, metadata examples)
- Lifecycle Hooks --- React to auth events with custom logic
- Geolocation --- Enable location data in audit records
- Admin Operations --- User management and session control