Blyp Docs

Clerk

Blyp resolves the authenticated user from each request using @clerk/backend and attaches the result to every log record for that request. No middleware plugin is required — Blyp calls Clerk's authenticateRequest directly at the framework handler level.

Install required peer package

bun add @clerk/backend

Setup

Use the clerk() factory to create the integration config, then pass it to the framework logger's auth.clerk option:

import { createLogger } from "@blyp/core/nextjs"; // or your framework
import { clerk } from "@blyp/core/clerk";

export const { logger, GET, POST } = createLogger({
  auth: {
    clerk: clerk({
      secretKey: process.env.CLERK_SECRET_KEY,
    }),
  },
});

clerk() config options

clerk({
  // Clerk client credentials — fall back to CLERK_SECRET_KEY env var if omitted
  secretKey: process.env.CLERK_SECRET_KEY,
  publishableKey: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
  jwtKey: process.env.CLERK_JWT_KEY,

  // Clerk API endpoint overrides (rarely needed)
  apiUrl: "https://api.clerk.com",
  apiVersion: "v1",

  // Satellite / proxy setup
  domain: process.env.CLERK_DOMAIN,
  proxyUrl: process.env.CLERK_PROXY_URL,
  isSatellite: false,

  // Token validation
  audience: process.env.CLERK_AUDIENCE,
  authorizedParties: ["https://your-app.com"],

  // Attach JWT claims to the auth context (default: false)
  includeClaims: false,

  // Attach the raw Clerk auth object to the context (default: false)
  includeRawAuth: false,

  // Fetch full User profile from Clerk API and cache it (default: false)
  hydrateUser: {
    cacheTtlMs: 30_000,   // how long to cache each user profile (default: 30s)
    maxEntries: 1_000,    // LRU cap on cached entries (default: 1,000)
  },

  // Custom authenticate request options — static or a per-request resolver
  authenticateRequestOptions: { acceptsToken: "session_token" },

  // Add custom fields to the auth context
  enrich: async ({ auth, request }) => ({
    role: auth?.orgRole ?? null,
  }),
})

Config fields

Auth context shape

// Authenticated user
{
  provider: "clerk",
  authenticated: true,
  actor: { kind: "user", id: "user_abc", email?: "...", name?: "..." },
  session: { id: "sess_xyz", activeOrganizationId?: "org_123" },
  organization: { id?: "org_123", slug?: "acme", role?: "admin" },
  impersonator: { id?: "impersonator_id" }, // set during impersonation
  lookup: {
    provider: "clerk",
    actorId: "user_abc",
    actorKind: "user",
    userId: "user_abc",
    sessionId: "sess_xyz",
    organizationId?: "org_123",
    tokenType: "session_token",
    email?: "...",
  },
  clerk: {
    tokenType: "session_token",
    orgPermissions?: ["org:feature:read"],
    factorVerificationAge?: [0, 300],
    scopes?: null,
  },
}

// Machine-authenticated (API key, M2M token, etc.)
{
  provider: "clerk",
  authenticated: true,
  actor: { kind: "machine", id: "machine_abc" },
  lookup: { provider: "clerk", actorId: "machine_abc", actorKind: "machine", tokenType: "api_key" },
  clerk: { tokenType: "api_key" },
}

// Unauthenticated
{
  provider: "clerk",
  authenticated: false,
  actor: { kind: "anonymous" },
  lookup: { provider: "clerk" },
}

User hydration

When hydrateUser is enabled, Blyp fetches the full User object from Clerk's Backend API after resolving the session and merges profile fields (email, name, fullName, firstName, lastName) into auth.actor.

Results are cached in an in-memory LRU store bounded by maxEntries and cacheTtlMs. The cache is per-process and resets on restart. On cache miss Blyp makes one Clerk API call per user; on failure the miss is cached for cacheTtlMs so one bad response does not hammer the API.

Enable hydrateUser only when you need profile-level fields in your logs. userId and sessionId from the session token are sufficient for most use cases.

Client-side logging

createClerkClientLogger creates a browser logger that posts logs to your Blyp ingestion endpoint. It is a thin wrapper around createClientLogger with a Clerk-appropriate default path:

import { createClerkClientLogger } from "@blyp/core/clerk";

const logger = createClerkClientLogger({
  endpoint: "/blyp/log",   // default
  connector: "betterstack", // optional — forward to a connector
});

logger.info("user clicked checkout");

createClerkClientLogger options

identifyUser

identifyUser extracts a ClerkLookupDescriptor from any object — useful for querying stored log rows by Clerk identity:

import { identifyUser } from "@blyp/core/clerk";

// Works on Blyp log records (normalized shape)
const descriptor = identifyUser(logRow);
// → { provider: "clerk", userId: "user_abc", sessionId: "sess_xyz", ... } | null

// Also works on flat database column shapes
const descriptor2 = identifyUser({
  authProvider: "clerk",
  authActorId: "user_abc",
  authSessionId: "sess_xyz",
  authOrganizationId: "org_123",
  authActorKind: "user",
  authTokenType: "session_token",
});

Return shape

{
  provider: "clerk",
  actorId?: string,          // actor's ID (userId for users, machineId for machines)
  actorKind?: "user" | "machine",
  userId?: string,
  sessionId?: string,
  organizationId?: string,
  tokenType?: string,
  email?: string,           // present when hydrateUser was enabled at log time
}

Returns null if the record has no Clerk auth context.

Framework support

Clerk auth resolution is supported in:

Notes