feat: add auth performance observability
This commit is contained in:
@@ -73,6 +73,23 @@ Non-secret defaults live in `wrangler.jsonc`:
|
||||
- `MAIL_FROM`
|
||||
- `CAPTCHA_PROVIDER`
|
||||
|
||||
Performance-related non-secret defaults also live in `wrangler.jsonc`:
|
||||
|
||||
- `BETTER_AUTH_SESSION_COOKIE_CACHE_MAX_AGE`: Better Auth session cookie cache TTL in seconds. Default is `300`. Lower it if session revocation or role changes must propagate faster.
|
||||
- `API_KEY_DEFER_UPDATES`: When `true`, API key request counters and timestamps are deferred through Better Auth background tasks and Worker `waitUntil`.
|
||||
|
||||
Auth request latency is logged as structured JSON with:
|
||||
|
||||
- `event=auth_request`
|
||||
- `method`
|
||||
- `path`
|
||||
- `status`
|
||||
- `durationMs`
|
||||
- optional `colo`
|
||||
- optional `cfRay`
|
||||
|
||||
Do not log request bodies, cookies, authorization headers, API keys, emails, phone numbers, or OTP codes.
|
||||
|
||||
Set secrets with Wrangler:
|
||||
|
||||
```bash
|
||||
@@ -111,3 +128,14 @@ pnpm deploy
|
||||
```
|
||||
|
||||
After deploy, repeat the runtime smoke checks against the production `BETTER_AUTH_URL`.
|
||||
|
||||
## Performance Validation
|
||||
|
||||
After deployment, compare p50, p95, and p99 for:
|
||||
|
||||
- `/api/auth/get-session`
|
||||
- `/api/auth/api-key/verify`
|
||||
- phone OTP endpoints
|
||||
- organization invitation endpoints
|
||||
|
||||
Enable Cloudflare Smart Placement only after logs show that latency is dominated by D1 or external provider round trips. Evaluate D1 read replication or secondary storage only after the first-stage cache and deferred-update changes are measured in production.
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Hono } from "hono";
|
||||
import { cors } from "hono/cors";
|
||||
import { createAuth } from "./auth";
|
||||
import { trustedOrigins, type Env } from "./env";
|
||||
import { withAuthRequestLogging } from "./observability";
|
||||
|
||||
const app = new Hono<{ Bindings: Env }>();
|
||||
|
||||
@@ -24,7 +25,7 @@ app.on(["POST", "GET"], "/api/auth/*", (c) => {
|
||||
c.executionCtx.waitUntil(promise);
|
||||
},
|
||||
});
|
||||
return auth.handler(c.req.raw);
|
||||
return withAuthRequestLogging(c.req.raw, () => auth.handler(c.req.raw));
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
35
src/observability.ts
Normal file
35
src/observability.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export type AuthRequestHandler = () => Response | Promise<Response>;
|
||||
|
||||
export async function withAuthRequestLogging(
|
||||
request: Request,
|
||||
handler: AuthRequestHandler,
|
||||
): Promise<Response> {
|
||||
const startedAt = Date.now();
|
||||
let status = 500;
|
||||
|
||||
try {
|
||||
const response = await handler();
|
||||
status = response.status;
|
||||
return response;
|
||||
} finally {
|
||||
logAuthRequest(request, status, Date.now() - startedAt);
|
||||
}
|
||||
}
|
||||
|
||||
function logAuthRequest(request: Request, status: number, durationMs: number): void {
|
||||
try {
|
||||
const event = {
|
||||
event: "auth_request",
|
||||
method: request.method,
|
||||
path: new URL(request.url).pathname,
|
||||
status,
|
||||
durationMs,
|
||||
colo: request.cf?.colo,
|
||||
cfRay: request.headers.get("cf-ray") ?? undefined,
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(event));
|
||||
} catch {
|
||||
// Observability must not affect auth responses.
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import worker from "../src/index";
|
||||
import type { Env } from "../src/env";
|
||||
|
||||
@@ -15,6 +15,16 @@ const env: Env = {
|
||||
TRUSTED_ORIGINS: "http://localhost:8787",
|
||||
};
|
||||
|
||||
let log: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
log = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("cfw-auth worker", () => {
|
||||
it("does not expose a custom health endpoint", async () => {
|
||||
const response = await worker.fetch(new Request("http://auth.local/healthz"), env);
|
||||
@@ -89,4 +99,39 @@ describe("cfw-auth worker", () => {
|
||||
);
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
it("logs auth request timing without changing the response", async () => {
|
||||
const response = await worker.fetch(
|
||||
new Request("http://auth.local/api/auth/reference", {
|
||||
headers: {
|
||||
"cf-ray": "test-ray",
|
||||
Authorization: "Bearer secret-token",
|
||||
Cookie: "better-auth.session_token=secret-cookie",
|
||||
},
|
||||
}),
|
||||
env,
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("content-type") ?? "").toContain("text/html");
|
||||
expect(log).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [rawMessage] = log.mock.calls[0] ?? [];
|
||||
expect(typeof rawMessage).toBe("string");
|
||||
const message = String(rawMessage);
|
||||
const event = JSON.parse(message) as Record<string, unknown>;
|
||||
|
||||
expect(event).toMatchObject({
|
||||
event: "auth_request",
|
||||
method: "GET",
|
||||
path: "/api/auth/reference",
|
||||
status: 200,
|
||||
cfRay: "test-ray",
|
||||
});
|
||||
expect(typeof event.durationMs).toBe("number");
|
||||
expect(message).not.toContain("secret-token");
|
||||
expect(message).not.toContain("secret-cookie");
|
||||
expect(message.toLowerCase()).not.toContain("authorization");
|
||||
expect(message.toLowerCase()).not.toContain("cookie");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
{
|
||||
"$schema": "node_modules/wrangler/config-schema.json",
|
||||
"name": "cfw-auth",
|
||||
"account_id": "67720b647ff2b55cf37ba3ef9e677083",
|
||||
"main": "src/index.ts",
|
||||
"compatibility_date": "2026-06-10",
|
||||
"compatibility_flags": [
|
||||
"nodejs_compat"
|
||||
],
|
||||
"observability": {
|
||||
"enabled": true,
|
||||
"head_sampling_rate": 0.1
|
||||
},
|
||||
"vars": {
|
||||
"BETTER_AUTH_URL": "http://localhost:8788",
|
||||
"TRUSTED_ORIGINS": "http://localhost:8787",
|
||||
"MAIL_PROVIDER": "resend",
|
||||
"MAIL_FROM": "noreply@example.com",
|
||||
"CAPTCHA_PROVIDER": "cloudflare-turnstile"
|
||||
"CAPTCHA_PROVIDER": "cloudflare-turnstile",
|
||||
"BETTER_AUTH_SESSION_COOKIE_CACHE_MAX_AGE": "300",
|
||||
"API_KEY_DEFER_UPDATES": "true"
|
||||
},
|
||||
"d1_databases": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user