Enterprises
Audit Logs & Compliance
Enterprise-grade audit logging with WorkOS for compliance, security monitoring, and incident response supporting SOC 2, HIPAA, and GDPR requirements
Audit Logs & Compliance
Enterprise audit logging provides comprehensive tracking of all security-relevant events across your application, meeting compliance requirements for SOC 2, HIPAA, GDPR, and other regulatory frameworks.
Overview
Audit logs enable:
- Compliance Requirements - Meet SOC 2, HIPAA, GDPR, ISO 27001 audit requirements
- Security Monitoring - Detect suspicious activity and security incidents
- Forensic Analysis - Investigate security breaches and data access
- User Activity Tracking - Monitor who accessed what and when
- Change History - Track all configuration and data modifications
- Regulatory Reporting - Generate compliance reports for auditors
Event Types
Authentication Events
// User login
{
type: 'auth.login.success',
actor: {
id: 'user_01ABC...',
email: '[email protected]',
organizationId: 'org_01XYZ...'
},
timestamp: '2025-01-20T14:30:00.000Z',
metadata: {
authMethod: 'sso',
ipAddress: '203.0.113.45',
userAgent: 'Mozilla/5.0...',
location: 'San Francisco, CA, US'
}
}
// Failed login attempt
{
type: 'auth.login.failed',
actor: {
email: '[email protected]' // User may not exist
},
timestamp: '2025-01-20T14:28:00.000Z',
metadata: {
reason: 'invalid_credentials',
ipAddress: '203.0.113.45',
attemptCount: 3
}
}
// Multi-factor authentication
{
type: 'auth.mfa.verified',
actor: { id: 'user_01ABC...', email: '[email protected]' },
timestamp: '2025-01-20T14:30:05.000Z',
metadata: {
method: 'totp', // or 'sms', 'push'
deviceId: 'device_01DEF...'
}
}Data Access Events
// Sensitive data accessed
{
type: 'data.accessed',
actor: { id: 'user_01ABC...', email: '[email protected]' },
target: {
type: 'customer_record',
id: 'customer_01GHI...',
classification: 'pii' // or 'phi', 'pci', 'confidential'
},
timestamp: '2025-01-20T15:00:00.000Z',
metadata: {
fields: ['ssn', 'creditCardNumber'],
reason: 'customer_support_request',
ticketId: 'TICKET-12345'
}
}
// Data exported
{
type: 'data.exported',
actor: { id: 'user_01ABC...', email: '[email protected]' },
target: { type: 'customer_list', count: 1500 },
timestamp: '2025-01-20T16:00:00.000Z',
metadata: {
format: 'csv',
filters: { segment: 'enterprise_customers' },
recordCount: 1500
}
}Permission Changes
// Role assigned
{
type: 'permission.role.assigned',
actor: { id: 'user_admin_01...', email: '[email protected]' },
target: { id: 'user_01ABC...', email: '[email protected]' },
timestamp: '2025-01-20T10:00:00.000Z',
metadata: {
role: 'admin',
previousRole: 'member',
reason: 'promotion_to_team_lead'
}
}
// Permission granted
{
type: 'permission.granted',
actor: { id: 'user_admin_01...', email: '[email protected]' },
target: { id: 'user_01ABC...', email: '[email protected]' },
timestamp: '2025-01-20T10:05:00.000Z',
metadata: {
permission: 'billing:manage',
resource: 'organization_01XYZ...',
expiresAt: '2025-02-20T10:05:00.000Z'
}
}Configuration Changes
// SSO configuration changed
{
type: 'config.sso.updated',
actor: { id: 'user_admin_01...', email: '[email protected]' },
target: { type: 'sso_connection', id: 'conn_01ABC...' },
timestamp: '2025-01-20T09:00:00.000Z',
metadata: {
changes: {
domains: {
before: ['acme.com'],
after: ['acme.com', 'acme.co.uk']
}
}
}
}
// Security policy updated
{
type: 'config.security.updated',
actor: { id: 'user_admin_01...', email: '[email protected]' },
target: { type: 'organization', id: 'org_01XYZ...' },
timestamp: '2025-01-20T09:30:00.000Z',
metadata: {
changes: {
requireMFA: { before: false, after: true },
sessionTimeout: { before: 86400, after: 3600 }
}
}
}Implementing Audit Logging
Basic Logging
import { WorkOS } from '@workos-inc/node'
const workos = new WorkOS(process.env.WORKOS_API_KEY)
// Create audit log event
async function logEvent(event) {
await workos.auditLogs.createEvent({
organizationId: event.organizationId,
event: {
action: event.type,
actor: {
id: event.actor.id,
name: event.actor.name,
type: 'user'
},
targets: event.targets?.map(t => ({
id: t.id,
type: t.type,
name: t.name
})),
context: {
location: event.metadata?.location,
userAgent: event.metadata?.userAgent,
ipAddress: event.metadata?.ipAddress
},
metadata: event.metadata,
occurredAt: event.timestamp || new Date().toISOString()
}
})
}
// Example: Log user login
on($.User.login, async ({ user, request }) => {
await logEvent({
type: 'user.login',
organizationId: user.organizationId,
actor: {
id: user.id,
name: `${user.firstName} ${user.lastName}`,
email: user.email
},
timestamp: new Date().toISOString(),
metadata: {
authMethod: user.authMethod,
ipAddress: request.headers['cf-connecting-ip'],
userAgent: request.headers['user-agent'],
location: request.cf?.city
}
})
})Automatic Event Tracking
Track all database operations automatically:
// Intercept all database writes
on($.Database.before.write, async ({ operation, collection, data, user }) => {
// Determine event type
const eventType = {
create: 'data.created',
update: 'data.updated',
delete: 'data.deleted'
}[operation]
// Log the event
await logEvent({
type: eventType,
organizationId: user.organizationId,
actor: {
id: user.id,
name: user.name,
email: user.email
},
targets: [{
id: data.id,
type: collection,
name: data.name || data.title || data.id
}],
metadata: {
changes: operation === 'update' ? data._changes : undefined,
previousValue: operation === 'delete' ? data : undefined
}
})
})Sensitive Data Access
Track access to sensitive fields:
const sensitiveFields = {
User: ['ssn', 'taxId', 'password'],
CreditCard: ['number', 'cvv'],
HealthRecord: ['diagnosis', 'medications']
}
on($.Database.after.read, async ({ collection, data, fields, user }) => {
const accessed = fields.filter(f =>
sensitiveFields[collection]?.includes(f)
)
if (accessed.length > 0) {
await logEvent({
type: 'data.sensitive.accessed',
organizationId: user.organizationId,
actor: {
id: user.id,
name: user.name,
email: user.email
},
targets: [{
id: data.id,
type: collection,
classification: 'sensitive'
}],
metadata: {
fields: accessed,
dataClassification: 'pii' // or 'phi', 'pci'
}
})
}
})Querying Audit Logs
Via WorkOS API
// Get recent events
const events = await workos.auditLogs.getEvents({
organizationId: 'org_01XYZ...',
limit: 100,
order: 'desc',
rangeStart: '2025-01-01T00:00:00.000Z',
rangeEnd: '2025-01-31T23:59:59.000Z'
})
// Filter by event type
const loginEvents = await workos.auditLogs.getEvents({
organizationId: 'org_01XYZ...',
actions: ['user.login', 'user.logout'],
limit: 100
})
// Filter by actor
const userEvents = await workos.auditLogs.getEvents({
organizationId: 'org_01XYZ...',
actorId: 'user_01ABC...',
limit: 100
})
// Filter by target
const resourceEvents = await workos.auditLogs.getEvents({
organizationId: 'org_01XYZ...',
targetId: 'project_01DEF...',
limit: 100
})Exporting Logs
// Export to CSV
async function exportAuditLogs(organizationId, startDate, endDate) {
const events = []
let after = null
// Fetch all events (paginated)
while (true) {
const response = await workos.auditLogs.getEvents({
organizationId,
rangeStart: startDate,
rangeEnd: endDate,
limit: 1000,
after
})
events.push(...response.data)
if (!response.listMetadata.after) break
after = response.listMetadata.after
}
// Convert to CSV
const csv = [
'Timestamp,Event Type,Actor,Target,IP Address,Location',
...events.map(e => [
e.occurredAt,
e.action,
e.actor.name,
e.targets?.[0]?.name || '',
e.context.ipAddress,
e.context.location
].join(','))
].join('\n')
return csv
}Audit Log Streaming
Stream audit logs to external systems:
To S3
// Stream to S3 for long-term retention
on($.AuditLog.created, async ({ event }) => {
const s3Key = `audit-logs/${event.organizationId}/${event.occurredAt.split('T')[0]}/${event.id}.json`
await env.S3.put(s3Key, JSON.stringify(event, null, 2), {
httpMetadata: {
contentType: 'application/json'
}
})
})To SIEM (Splunk, Datadog, etc.)
// Forward to SIEM for real-time monitoring
on($.AuditLog.created, async ({ event, organization }) => {
const siem = organization.settings.siem
if (siem?.enabled) {
await fetch(siem.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${siem.apiKey}`
},
body: JSON.stringify({
source: 'platform.do',
sourcetype: 'audit_log',
event
})
})
}
})To ClickHouse (Analytics)
// Store in ClickHouse for analytics
on($.AuditLog.created, async ({ event }) => {
await env.CLICKHOUSE.insert('audit_logs', {
timestamp: event.occurredAt,
organization_id: event.organizationId,
event_type: event.action,
actor_id: event.actor.id,
actor_email: event.actor.email,
target_id: event.targets?.[0]?.id,
target_type: event.targets?.[0]?.type,
ip_address: event.context.ipAddress,
location: event.context.location,
metadata: JSON.stringify(event.metadata)
})
})Compliance Reports
SOC 2 Report
async function generateSOC2Report(organizationId, startDate, endDate) {
// Trust Services Criteria: Security
const securityEvents = await workos.auditLogs.getEvents({
organizationId,
rangeStart: startDate,
rangeEnd: endDate,
actions: [
'auth.login.failed',
'auth.mfa.failed',
'permission.denied',
'data.unauthorized_access'
]
})
// Trust Services Criteria: Availability
const availabilityMetrics = await db.query(`
SELECT
COUNT(*) FILTER (WHERE event_type = 'system.outage') as outages,
SUM(duration) FILTER (WHERE event_type = 'system.outage') as total_downtime
FROM events
WHERE organization_id = $1
AND timestamp >= $2
AND timestamp <= $3
`, [organizationId, startDate, endDate])
// Trust Services Criteria: Processing Integrity
const dataEvents = await workos.auditLogs.getEvents({
organizationId,
rangeStart: startDate,
rangeEnd: endDate,
actions: [
'data.created',
'data.updated',
'data.deleted',
'data.exported'
]
})
// Trust Services Criteria: Confidentiality
const accessEvents = await workos.auditLogs.getEvents({
organizationId,
rangeStart: startDate,
rangeEnd: endDate,
actions: [
'data.sensitive.accessed',
'permission.role.assigned',
'config.security.updated'
]
})
return {
period: { startDate, endDate },
security: {
failedLogins: securityEvents.data.filter(e => e.action === 'auth.login.failed').length,
unauthorizedAccess: securityEvents.data.filter(e => e.action === 'data.unauthorized_access').length
},
availability: {
outages: availabilityMetrics.outages,
totalDowntime: availabilityMetrics.total_downtime
},
processing: {
recordsCreated: dataEvents.data.filter(e => e.action === 'data.created').length,
recordsUpdated: dataEvents.data.filter(e => e.action === 'data.updated').length,
recordsDeleted: dataEvents.data.filter(e => e.action === 'data.deleted').length
},
confidentiality: {
sensitiveDataAccess: accessEvents.data.filter(e => e.action === 'data.sensitive.accessed').length,
permissionChanges: accessEvents.data.filter(e => e.action === 'permission.role.assigned').length
}
}
}HIPAA Audit Trail
async function generateHIPAAReport(organizationId, startDate, endDate) {
// 45 CFR § 164.312(b) - Audit controls
const phi_access = await workos.auditLogs.getEvents({
organizationId,
rangeStart: startDate,
rangeEnd: endDate,
actions: [
'phi.accessed',
'phi.created',
'phi.updated',
'phi.deleted',
'phi.exported'
]
})
return {
period: { startDate, endDate },
requiredElements: {
// Who accessed
actors: [...new Set(phi_access.data.map(e => e.actor.id))],
// What was accessed
records: [...new Set(phi_access.data.map(e => e.targets?.[0]?.id))],
// When accessed
timestamps: phi_access.data.map(e => e.occurredAt),
// What action was performed
actions: phi_access.data.map(e => e.action),
// Where accessed from
locations: phi_access.data.map(e => e.context.ipAddress)
},
summary: {
totalAccesses: phi_access.data.length,
uniqueUsers: [...new Set(phi_access.data.map(e => e.actor.id))].length,
uniqueRecords: [...new Set(phi_access.data.map(e => e.targets?.[0]?.id))].length
}
}
}GDPR Data Access Report
// GDPR Article 15 - Right of access
async function generateGDPRDataAccessReport(userId, organizationId) {
const events = await workos.auditLogs.getEvents({
organizationId,
actorId: userId,
limit: 10000
})
return {
subject: {
userId,
requestDate: new Date().toISOString()
},
personalDataProcessing: {
purposes: [...new Set(events.data.map(e => e.action))],
categories: ['user_profile', 'authentication', 'activity_logs'],
recipients: ['internal_systems', 'cloud_providers'],
retentionPeriod: '2 years',
dataSubjectRights: [
'right_to_access',
'right_to_rectification',
'right_to_erasure',
'right_to_restrict_processing'
]
},
activityLog: events.data.map(e => ({
timestamp: e.occurredAt,
action: e.action,
target: e.targets?.[0]?.type,
purpose: e.metadata?.purpose
}))
}
}Retention Policies
Configure Retention
const retentionPolicies = {
authentication: {
events: ['auth.*'],
retention: 365, // days
reason: 'SOC 2 Type II requirement'
},
dataAccess: {
events: ['data.accessed', 'data.sensitive.accessed'],
retention: 730, // 2 years
reason: 'HIPAA requirement'
},
permissionChanges: {
events: ['permission.*', 'config.*'],
retention: 2555, // 7 years
reason: 'Legal requirement'
},
general: {
events: ['*'],
retention: 90,
reason: 'Standard retention'
}
}
// Enforce retention
on($.AuditLog.cleanup, async () => {
for (const [category, policy] of Object.entries(retentionPolicies)) {
const cutoffDate = subDays(new Date(), policy.retention)
await db.delete($.AuditLog, {
eventType: { in: policy.events },
createdAt: { lt: cutoffDate }
})
console.log(`Deleted ${category} events older than ${policy.retention} days`)
}
})Alerting & Monitoring
Real-Time Alerts
// Alert on suspicious activity
on($.AuditLog.created, async ({ event }) => {
// Multiple failed login attempts
if (event.action === 'auth.login.failed') {
const recentFailures = await db.count($.AuditLog, {
eventType: 'auth.login.failed',
'actor.email': event.actor.email,
createdAt: { gte: subMinutes(new Date(), 15) }
})
if (recentFailures >= 5) {
await send($.Alert.security, {
severity: 'high',
title: 'Multiple failed login attempts',
description: `${event.actor.email} had ${recentFailures} failed login attempts in 15 minutes`,
metadata: event
})
}
}
// Unusual data export
if (event.action === 'data.exported' && event.metadata.recordCount > 1000) {
await send($.Alert.security, {
severity: 'medium',
title: 'Large data export',
description: `${event.actor.email} exported ${event.metadata.recordCount} records`,
metadata: event
})
}
// Permission escalation
if (event.action === 'permission.role.assigned' && event.metadata.role === 'admin') {
await send($.Alert.security, {
severity: 'high',
title: 'Admin role assigned',
description: `${event.actor.email} granted admin role to ${event.targets[0].name}`,
metadata: event
})
}
})User Activity Timeline
Show users their own activity:
function UserActivityTimeline({ userId }) {
const [events, setEvents] = useState([])
useEffect(() => {
async function fetchActivity() {
const response = await fetch(`/api/audit-logs/user/${userId}`)
const data = await response.json()
setEvents(data)
}
fetchActivity()
}, [userId])
return (
<div className="activity-timeline">
<h2>Your Activity</h2>
{events.map(event => (
<div key={event.id} className="activity-item">
<time>{formatDate(event.occurredAt)}</time>
<span className="event-type">{event.action}</span>
<span className="event-description">
{formatEventDescription(event)}
</span>
<span className="event-location">
{event.context.location} ({event.context.ipAddress})
</span>
</div>
))}
</div>
)
}Related Documentation
- Enterprise SSO - Single Sign-On authentication
- Directory Sync - Automatic user provisioning
- Organizations - Multi-tenant organization management
- Admin Portal - Self-service configuration
- Security - Authentication and authorization
- WorkOS Audit Logs Docs - Official WorkOS documentation