Audit Plugin

Logging, monitoring and auditing your authorization policies


The audit plugin is used to receive events from the Kilpi server and send them to an external service. This is useful for logging, monitoring and auditing your authorization policies.

The audit plugin is already included in @kilpi/core. Apply the plugin in your Kilpi configuration as follows.

kilpi.ts
import { createKilpi, AuditPlugin } from "@kilpi/core"
export const Kilpi = createKilpi({
getSubject,
policies,
plugins: [AuditPlugin({ ... })]
})

The audit plugin is designed to primarily work independently after being configured. You however need to configure at the least the strategy and onFlushEvents callback.


Configure external service (onFlushEvents)

AuditPlugin is fully agnostic to the external service you want to send events to. Connect your audit, logging, or monitoring service with the onFlushEvents callback.

AuditPlugin({
async onFlushEvents(events) {
await fetch(`https://my-audit-service.com/send-events`, {
method: "POST",
body: JSON.stringify(events),
headers: { "Content-Type": "application/json" },
});
},
// ...
});

Configure a strategy

Sending every event immediately and separately is not efficient. Instead, AuditPlugin provides several different strategies for batching and flushing your events.

The recommended strategy is batching. When an event is received, a new batch is started. Every event after that event is added to the batch until batchTimeoutSeconds expires, at which point all events in the batch are sent to the external service.

AuditPlugin({
strategy: "batch",
batchTimeoutSeconds: 5,
async onFlushEvents(events) { ... },
})

You can also prematurely flush the current batch by calling Kilpi.audit.flush().

Periodical strategy

The periodical strategy sends events every flushIntervalSeconds seconds (if there are any events to send).

AuditPlugin({
strategy: "periodical",
flushIntervalSeconds: 5,
async onFlushEvents(events) { ... },
})

You can also prematurely flush the current batch by calling Kilpi.audit.flush().

Manual strategy

The manual strategy collects all events and waits until Kilpi.audit.flush() is manually called by you. This allows for full developer control on when events are flushed.

AuditPlugin({
strategy: "manual",
async onFlushEvents(events) { ... },
})
await Kilpi.audit.flush()

Immediate strategy

Finally, primarily for testing purposes, the immediate strategy sends every event separately and immediately as they come. Calling Kilpi.audit.flush() will have no effect, as no batching is done.

AuditPlugin({
strategy: "immediate",
async onFlushEvents(events) { ... },
})

Using in serverless environments

Batching in serverless environments is difficult. You have to ensure that the batch has time to flush and complete before the serverless function dies.

For this purpose, AuditPlugin allows you to pass a waitUntil function provided by some serverless environments, such as Vercel or Cloudflare. Pass it as the waitUntil option to the plugin.

import { waitUntil } from "@vercel/functions";
AuditPlugin({
strategy: "batch",
batchTimeoutSeconds: 5,
waitUntil, // (promise: Promise<unknown>) => void
async onFlushEvents(events) { ... },
})

Note for Next.js: Using after is possible however it will only work in RSCs.


Filtering events

You may optionally filter events using the filterEvents option. For example, to only include denied decisions you may do the following.

AuditPlugin({
...,
filterEvents(event) {
return !event.authorization.granted
}
})

Enabling and disabling

You may optionally provide the disabled option to disable the plugin. By default, the plugin is enabled.

AuditPlugin({
...,
disabled: true
})

To dynamically enable or disable the plugin, you can use the following methods.

Kilpi.audit.disable();
Kilpi.audit.enable();

Manual flushing

For more precise control on when the current batch is flushed, you may call the Kilpi.audit.flush() method to force an immediate flush.

// Flush on SIGINT before exiting
process.on("SIGINT", async () => {
await Kilpi.audit.flush();
process.exit(0);
});

This works with all strategies except the immediate strategy as it does not do batching.


Logging

You may be tempted to use the AuditPlugin for logging, and it can be done. However, logging authorization decisions using the onAfterAuthorization hook (used internally by AuditPlugin) is much easier. See example below.

kilpi.ts
const Kilpi = createKilpi({
...,
// Logging using AuditPlugin
plugins: [
AuditPlugin({
strategy: "immediate",
async onFlushEvents(events) {
for (const e of events) {
console.log(format(e))
}
}
}),
]
});
// Logging using hooks
Kilpi.hooks.onAfterAuthorization((e) => console.log(format(e)));