feat: add auth performance observability

This commit is contained in:
2026-06-10 19:47:17 -07:00
parent e3057928a2
commit 1fc05b7b3a
5 changed files with 119 additions and 3 deletions

View File

@@ -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.

View File

@@ -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
View 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.
}
}

View File

@@ -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");
});
});

View File

@@ -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": [
{