Skip to main content

OpenTelemetry

WIP

OtelInstrumentation auto-wires counters, up-down counters, and histograms onto the events bus. Redacted attributes by default.

Setup

import { metrics, trace } from '@opentelemetry/api'
import { OtelInstrumentation } from '@gentleduck/auth/telemetry/otel'

const otel = new OtelInstrumentation({
  meter: metrics.getMeter('@gentleduck/auth'),
  prefix: 'auth',
  defaultAttributes: { service: 'api', region: 'us-east-1' },
})

const unsubscribe = otel.attach(auth.events)
// later: unsubscribe()

Requires @opentelemetry/api as a peer dependency. It is lazy-loaded

  • apps that don't wire OTel pay no cost.

What it records

Every event on the bus translates to a metric:

EventMetricAttributes
session.createdauth.session.total (counter)kind, aal, provider
session.createdauth.session.active (up-down counter, +1)-
session.revokedauth.session.active (up-down counter, -1)-
session.rotatedauth.session.rotations.totalreason
password.changedauth.password.changes.total-
password.resetauth.password.resets.total-
mfa.enrolledauth.mfa.enrolled.totalfactor
mfa.verifiedauth.mfa.verified.totalfactor
anomalyauth.anomaly.signals.totaldetector, score
anomalyauth.anomaly.decisions.totaldecision (allow/step-up/deny)
lockoutauth.lockout.totalreason
identity.createdauth.identity.signups.total-

Plus a histogram per provider:

HistogramAttributes
auth.signin.duration_msprovider, result (success/failure), code
auth.provider.callback.duration_msprovider

Tracing

For trace-level observability, duck-auth emits a span around each provider call:

provider.password.complete  -> trace.spanId, trace.traceId
  |-- store.identity.findByEmail
  |-- store.credential.findByHashedSecret
  |-- hasher.verify
  \-- session.rotateOrCreate

Spans carry attributes:

  • auth.provider_id
  • auth.flow (signin / signout / refresh / provider-begin)
  • auth.result (success / failure)
  • auth.code (the AUTH/* code on failure)

PII attributes (email, ip, userAgent, profile fields) are redacted by default - the OTel instrumentation runs every span attribute through a denylist before emission. To override:

new OtelInstrumentation({
  meter,
  redact: {
    extraDenylist: ['custom-sensitive-field'],
    allowlist: [],     // never include these - even if you ask
  },
})

SOC2 / HIPAA

The SOC2 and HIPAA compliance presets configure OTel with the audit-log sink wired in. You get:

  • Every event emitted to OTel.
  • Every event also delivered via AuthWebhookDeliverer to a signed audit log endpoint.
  • PII redaction enforced at every layer.
import { authApplyCompliancePreset } from '@gentleduck/auth/core'

const cfg = authApplyCompliancePreset(base, 'soc2')
// result.events.middleware includes the PII redaction layer.

Dashboards

The metric names follow OTel semconv conventions where possible. A ready-made Grafana dashboard JSON ships under @gentleduck/auth/telemetry/otel/dashboards/grafana.json - import it into your Grafana instance to get:

  • Sign-in rate / failure rate, by provider.
  • Active sessions, by AAL.
  • Lockout count, with annotations for hijack-policy fires.
  • p50/p95/p99 sign-in latency, by provider.
  • Anomaly signal distribution.

Tag the dashboard's data source variable with your OTel collector's Prometheus endpoint.

Custom sinks

If you don't use OpenTelemetry, the same metrics can be wired by hand:

auth.events.on('session.created', () => {
  statsd.increment('auth.session.total', 1, ['aal:aal1'])
})

auth.events.on('lockout', (event) => {
  pagerduty.trigger('auth-lockout', {
    severity: 'warning',
    custom_details: event,
  })
})

Pick whichever observability stack you already operate.