@kubiks/otel-better-auth

One-line OpenTelemetry instrumentation for Better Auth, tracing every sign-in, sign-up, OAuth callback, and session operation.

Library
npm
v2.0.2
239stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
40/100Fair
Development Activity0
Maintenance48
Community36
Maturity36
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
58/100Fair
Architecture66
Code Quality68
Innovation58
Learning Curve40

@kubiks/otel-better-auth wraps a Better Auth instance with OpenTelemetry tracing so every authentication flow becomes observable without touching the rest of your auth configuration. A single instrumentBetterAuth() call patches all api.* methods (session lookups, email/OAuth sign-in and sign-up, password resets, account linking, and more) to emit spans carrying operation type, auth method, provider, user ID, session ID, and success/error status.

A companion otelPlugin() hooks into Better Auth’s HTTP request/response lifecycle to also trace OAuth callbacks and redirect-based flows that don’t go through the direct API surface, closing the gap that method-wrapping alone can’t cover. The package ships as a small, dependency-free TypeScript module built specifically to sit alongside Better Auth’s plugin system, part of Kubiks’ broader otel-* family of one-line OpenTelemetry integrations for the TypeScript ecosystem.

What You Get

  • instrumentBetterAuth(auth) — wraps an existing Better Auth instance in place, auto-detecting and tracing all api.* methods including ones not explicitly listed in its metadata map
  • otelPlugin() — a Better Auth plugin added via the plugins array that traces HTTP-level operations (OAuth initiation/callback, sign-in, sign-out, password/email flows) using request path matching
  • Rich span attributes on every trace — auth.operation, auth.method, auth.provider, auth.success, auth.error, user.id, user.email, session.id
  • Idempotent instrumentation — a hidden flag prevents double-wrapping if instrumentBetterAuth() is called more than once on the same instance
  • Custom tracer support — pass a tracerName or a pre-built Tracer instance instead of using the default trace.getTracer() tracer

Common Use Cases

  • Adding distributed tracing to a Next.js or Node app’s Better Auth setup to see auth latency and failures inside an existing OpenTelemetry pipeline
  • Debugging intermittent OAuth sign-in failures by inspecting auth.http.oauth.callback.{provider} spans for the specific provider and error message
  • Correlating authentication spans with downstream request traces (API calls, DB queries) to see the full request lifecycle from login to session-backed action
  • Monitoring password-reset and email-verification funnels in production dashboards built on OpenTelemetry-compatible backends (e.g. Honeycomb, Grafana Tempo, Datadog)

Under The Hood

Architecture The package is a single-file (src/index.ts) instrumentation layer with two independent entry points that compose rather than depend on each other. instrumentBetterAuth() walks the live auth.api object at runtime, wrapping each method with wrapAuthMethod(), which starts a span, runs the original call inside context.with(), and inspects the result shape ({data, error} from Better Fetch vs. a direct object) to pull out user/session IDs before closing the span. A parallel otelPlugin() registers as a genuine Better Auth plugin with onRequest/onResponse/after hooks, using a module-level Map<string, Span> keyed by method:url to bridge span state across the request/response lifecycle for HTTP-only flows like OAuth redirects that never touch auth.api directly. An idempotency flag (__kubiksOtelBetterAuthInstrumented) guards against double-wrapping if instrumentBetterAuth() runs twice, and ensureOtelPlugin() checks for an existing id: "otel" plugin before injecting its own, so the two entry points can be combined without duplicate spans.

Tech Stack Written in TypeScript, compiled with tsc to ESM output (dist/index.js plus a dist/types declaration folder) and published under the standard npm exports/main/types fields. It depends on @opentelemetry/api as a peer dependency (>=1.9.0 <2.0.0) so consumers control the actual SDK/exporter, and on better-auth (>=0.1.0) purely for its createAuthMiddleware helper and plugin/type surface. The package lives inside a pnpm + Turborepo monorepo (kubiks-inc/otel) alongside ten sibling @kubiks/otel-* integrations, sharing root-level tsconfig.json, turbo.json, and a GitHub Actions release pipeline that runs type-checking and unit tests per package.

Code Quality Tests live in src/index.test.ts (Vitest, ~250 lines) and cover the two main entry points with mocked Better Auth server objects, asserting span-wrapping behavior, the instrumented flag, and non-object/null inputs. There is no dedicated linter config visible in the package itself, but the repo runs tsc --noEmit type-checking and vitest --run in CI (.github/workflows/type-check.yml, unit-test.yml) on every change. strict mode is disabled in the package’s own tsconfig.json even though the monorepo root enables strict: true, and several internal helpers use any casts (AnyAuth = Auth<any>) to work around Better Auth’s complex generic types — a pragmatic but real type-safety compromise. Error handling in the HTTP-hook path is defensive: onRequest/onResponse/after hooks each wrap their body in try/catch and log to console.error rather than throwing, so a tracing bug can’t take down an auth request.

What Makes It Unique Most OpenTelemetry auto-instrumentation packages patch a library’s internals or monkey-patch require(); this package instead uses Better Auth’s own plugin system (otelPlugin()) combined with direct method-table wrapping, which means it stays correct across Better Auth’s frequent API surface changes as long as the plugin contract holds. The dual-path design — API-method wrapping for direct calls, HTTP-hook tracing for redirect-based OAuth flows the API surface can’t see — is a deliberate answer to a real gap: OAuth callbacks return an HTTP redirect, not a value the caller awaits, so span attribution has to happen out-of-band via the request/response Map. It is a narrow, single-purpose package rather than a general-purpose tracing framework, which is standard for the instrumentation-package category it sits in.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search