@authhero/cloudflare-adapter

中文文档
4.2.0发布于 2026/11/05AGPL-3.0-onlyTypeScriptESM + CJS构建来源已验证近 30 天下载 1716

Cloudflare-specific adapters for AuthHero, providing integrations with Cloudflare services.

原始使用文档
文档目录30 节
  1. Features
  2. Installation
  3. Usage
  4. With Analytics Engine Logs (Recommended for Workers)
  5. With R2 SQL Logs (HTTP Endpoint Mode)
  6. Service Binding Mode (Cloudflare Workers)
  7. Passthrough Mode (Multiple Destinations)
  8. Adapters
  9. Custom Domains Adapter
  10. Cache Adapter
  11. Logs Adapter (R2 SQL + Pipelines)
  12. Logs Adapter (Analytics Engine)
  13. Outbox Metrics Sink (Analytics Engine)
  14. Setup
  15. Data Schema
  16. Geo Adapter
  17. Features
  18. Cloudflare Setup
  19. Configuration
  20. Cloudflare Headers Used
  21. Response Format
  22. Integration with AuthHero
  23. Alternative: IP Geolocation Databases
  24. Environment Variables
  25. TypeScript
  26. Code Executors
  27. WorkerLoaderCodeExecutor — Dynamic Workers
  28. DispatchNamespaceCodeExecutor — Workers for Platforms
  29. Related Documentation
  30. License

@authhero/cloudflare-adapter

Cloudflare-specific adapters for AuthHero, providing integrations with Cloudflare services.

Features

This package provides adapters for:

  • Custom Domains - Manage custom domains via Cloudflare API
  • Cache - Caching using Cloudflare's Cache API
  • Geo (optional) - Extract geographic information from Cloudflare request headers
  • Logs (optional) - Two options for authentication logs:
    • Analytics Engine - Low-latency writes with SQL querying (90-day retention)
    • R2 SQL + Pipelines - Long-term storage with unlimited retention

Installation

npm install @authhero/cloudflare-adapter

Usage

import createAdapters from "@authhero/cloudflare-adapter";

// In a Cloudflare Worker
interface Env {
  AUTH_LOGS: AnalyticsEngineDataset;
  CLOUDFLARE_ACCOUNT_ID: string;
  ANALYTICS_ENGINE_API_TOKEN: string;
}

export default {
  async fetch(request: Request, env: Env) {
    const adapters = createAdapters({
      // Custom domains configuration
      zoneId: "your-cloudflare-zone-id",
      authKey: "your-cloudflare-api-key",
      authEmail: "your-cloudflare-email",
      customDomainAdapter: yourDatabaseCustomDomainsAdapter,

      // Analytics Engine logs (low latency writes)
      analyticsEngineLogs: {
        analyticsEngineBinding: env.AUTH_LOGS,
        accountId: env.CLOUDFLARE_ACCOUNT_ID,
        apiToken: env.ANALYTICS_ENGINE_API_TOKEN,
      },
    });

    const { customDomains, cache, geo, logs } = adapters;
    // ...
  },
};

With R2 SQL Logs (HTTP Endpoint Mode)

import createAdapters from "@authhero/cloudflare-adapter";

const adapters = createAdapters({
  // Custom domains configuration
  zoneId: "your-cloudflare-zone-id",
  authKey: "your-cloudflare-api-key",
  authEmail: "your-cloudflare-email",
  customDomainAdapter: yourDatabaseCustomDomainsAdapter,

  // Cache configuration (optional)
  cacheName: "default",
  defaultTtlSeconds: 3600,
  keyPrefix: "authhero:",

  // Geo adapter configuration (optional) - automatically included when getHeaders is provided
  getHeaders: () => {
    // In Cloudflare Workers, you'd typically pass request headers
    // Cloudflare automatically adds cf-ipcountry, cf-ipcity, etc.
    return request.headers;
  },

  // R2 SQL logs configuration (optional) - HTTP mode
  r2SqlLogs: {
    pipelineEndpoint: "https://your-stream-id.ingest.cloudflare.com",
    authToken: process.env.R2_SQL_AUTH_TOKEN,
    warehouseName: process.env.R2_WAREHOUSE_NAME,
    namespace: "default",
    tableName: "logs",
  },
});

// Use the adapters
const { customDomains, cache, geo, logs } = adapters;

Service Binding Mode (Cloudflare Workers)

interface Env {
  PIPELINE_SERVICE: { fetch: typeof fetch };
  R2_SQL_AUTH_TOKEN: string;
  R2_WAREHOUSE_NAME: string;
}

export default {
  async fetch(request: Request, env: Env) {
    const adapters = createAdapters({
      zoneId: "your-cloudflare-zone-id",
      authKey: "your-cloudflare-api-key",
      authEmail: "your-cloudflare-email",
      customDomainAdapter: yourDatabaseCustomDomainsAdapter,

      // Geo adapter - extract location from Cloudflare headers
      getHeaders: () => Object.fromEntries(request.headers),

      // R2 SQL logs with service binding
      r2SqlLogs: {
        pipelineBinding: env.PIPELINE_SERVICE,
        authToken: env.R2_SQL_AUTH_TOKEN,
        warehouseName: env.R2_WAREHOUSE_NAME,
      },
    });

    // Use adapters.logs and adapters.geo
  },
};

Passthrough Mode (Multiple Destinations)

Use the core createPassthroughAdapter utility to sync logs to multiple destinations:

import { createPassthroughAdapter } from "@authhero/adapter-interfaces";
import createAdapters, {
  createR2SQLLogsAdapter,
  createAnalyticsEngineLogsAdapter,
} from "@authhero/cloudflare-adapter";

// Primary adapter (e.g., existing database)
const databaseAdapter = createDatabaseLogsAdapter();

// Cloudflare logs adapters for secondary syncing
const r2SqlAdapter = createR2SQLLogsAdapter({
  pipelineEndpoint: "https://your-stream-id.ingest.cloudflare.com",
  authToken: process.env.R2_SQL_AUTH_TOKEN,
  warehouseName: process.env.R2_WAREHOUSE_NAME,
});

const analyticsEngineAdapter = createAnalyticsEngineLogsAdapter({
  analyticsEngineBinding: env.AUTH_LOGS,
  accountId: env.CLOUDFLARE_ACCOUNT_ID,
  apiToken: env.ANALYTICS_ENGINE_API_TOKEN,
});

// Create passthrough adapter - writes to primary and all secondaries
const logsAdapter = createPassthroughAdapter({
  primary: databaseAdapter,
  secondaries: [
    { adapter: { create: r2SqlAdapter.create } },
    { adapter: { create: analyticsEngineAdapter.create } },
  ],
});

// logsAdapter.create() writes to database, R2 SQL Pipeline, and Analytics Engine
// logsAdapter.get() and logsAdapter.list() read from database only

Adapters

Custom Domains Adapter

Manages custom domains through the Cloudflare API.

Configuration

{
  zoneId: string;              // Your Cloudflare zone ID
  authKey: string;             // Your Cloudflare API key
  authEmail: string;           // Your Cloudflare account email
  enterprise?: boolean;        // Whether using Cloudflare Enterprise (default: false)
  customDomainAdapter: CustomDomainsAdapter; // Database adapter for storing domain data
}

Methods

  • create(tenantId, domain) - Create a custom domain
  • get(tenantId, domainId) - Get a custom domain
  • getByDomain(domain) - Get domain by domain name
  • list(tenantId, params) - List custom domains
  • remove(tenantId, domainId) - Remove a custom domain
  • update(tenantId, domainId, data) - Update a custom domain

Cache Adapter

Provides caching using Cloudflare's Cache API.

Configuration

{
  cacheName?: string;          // Cache name (default: "default")
  defaultTtlSeconds?: number;  // Default TTL in seconds (default: undefined)
  keyPrefix?: string;          // Key prefix for namespacing (default: undefined)
}

Methods

  • get<T>(key) - Get a value from cache
  • set<T>(key, value, ttl?) - Set a value in cache with optional TTL
  • delete(key) - Delete a value from cache

Example

// Set a value with 1 hour TTL
await cache.set("user:123", userData, 3600);

// Get a value
const user = await cache.get("user:123");

// Delete a value
await cache.delete("user:123");

Logs Adapter (R2 SQL + Pipelines)

Write authentication logs to Cloudflare R2 using Pipelines for ingestion and R2 SQL for querying.

Architecture

This adapter uses two Cloudflare services:

  • Pipelines: HTTP endpoint for ingesting log data into R2
  • R2 SQL: Query interface for analyzing logs stored in Apache Iceberg format

Prerequisites

  1. Create an R2 bucket and enable R2 Data Catalog:
npx wrangler r2 bucket create my-authhero-logs
npx wrangler r2 bucket catalog enable my-authhero-logs

Note the "Warehouse" name from the output - you'll need it later.

  1. Create an API token

In the Cloudflare Dashboard:

  • Go to R2 object storage
  • Select "Manage API tokens"
  • Create a token with Admin Read & Write permissions
  • Save the token value
  1. Create a schema file (schema.json):
{
  "fields": [
    { "name": "tenant_id", "type": "string", "required": true },
    { "name": "type", "type": "string", "required": true },
    { "name": "date", "type": "string", "required": true },
    { "name": "description", "type": "string", "required": false },
    { "name": "ip", "type": "string", "required": false },
    { "name": "user_agent", "type": "string", "required": true },
    { "name": "details", "type": "string", "required": false },
    { "name": "isMobile", "type": "int64", "required": false },
    { "name": "user_id", "type": "string", "required": false },
    { "name": "user_name", "type": "string", "required": false },
    { "name": "connection", "type": "string", "required": false },
    { "name": "connection_id", "type": "string", "required": false },
    { "name": "client_id", "type": "string", "required": false },
    { "name": "client_name", "type": "string", "required": false },
    { "name": "audience", "type": "string", "required": false },
    { "name": "scope", "type": "string", "required": false },
    { "name": "strategy", "type": "string", "required": false },
    { "name": "strategy_type", "type": "string", "required": false },
    { "name": "hostname", "type": "string", "required": false },
    { "name": "auth0_client", "type": "string", "required": false },
    { "name": "log_id", "type": "string", "required": true },
    { "name": "country_code", "type": "string", "required": false },
    { "name": "city_name", "type": "string", "required": false },
    { "name": "latitude", "type": "string", "required": false },
    { "name": "longitude", "type": "string", "required": false },
    { "name": "time_zone", "type": "string", "required": false },
    { "name": "continent_code", "type": "string", "required": false }
  ]
}
  1. Create a Pipeline:
npx wrangler pipelines setup

Follow the prompts:

  • Pipeline name: authhero-logs
  • Enable HTTP endpoint: yes
  • Require authentication: no (or yes if you want additional security)
  • Configure custom CORS origins: no
  • Schema definition: Load from file
  • Schema file path: schema.json
  • Destination type: Data Catalog Table
  • R2 bucket name: my-authhero-logs
  • Namespace: default
  • Table name: logs
  • Catalog API token: Enter your token from step 2
  • Compression: zstd
  • Roll file when size reaches (MB): 100
  • Roll file when time reaches (seconds): 300 (5 minutes)
  • SQL transformation: Use simple ingestion query

Important: Save the HTTP endpoint URL from the output (e.g., https://abc123.ingest.cloudflare.com)

Configuration

The R2 SQL logs adapter supports three usage modes:

1. HTTP Endpoint Mode (Default)

Use this mode when calling the Pipeline from outside a Cloudflare Worker:

{
  pipelineEndpoint: string;    // Pipeline HTTP endpoint URL for ingesting logs
  authToken: string;           // R2 SQL API token for querying logs
  warehouseName: string;       // R2 warehouse name
  namespace?: string;          // Catalog namespace (default: "default")
  tableName?: string;          // Table name (default: "logs")
  apiBaseUrl?: string;         // R2 SQL API base URL
  timeout?: number;            // HTTP timeout in ms (default: 30000)
}
2. Pipeline Binding Mode (Workers)

Use this mode when running inside a Cloudflare Worker with a Pipeline binding. This is the most efficient mode as it uses direct bindings without HTTP overhead.

wrangler.toml:

[[pipelines]]
binding = "AUTH_LOGS_STREAM"
pipeline = "your-pipeline-id"

TypeScript:

interface Env {
  AUTH_LOGS_STREAM: Pipeline;
  R2_SQL_AUTH_TOKEN: string;
  R2_WAREHOUSE_NAME: string;
}

export default {
  async fetch(request: Request, env: Env) {
    const adapters = createAdapters({
      zoneId: "your-zone-id",
      authKey: "your-api-key",
      authEmail: "your-email",
      customDomainAdapter: yourDbAdapter,

      r2SqlLogs: {
        pipelineBinding: env.AUTH_LOGS_STREAM,
        authToken: env.R2_SQL_AUTH_TOKEN,
        warehouseName: env.R2_WAREHOUSE_NAME,
      },
    });

    const { logs } = adapters;
    // Use logs adapter
  },
};

The Pipeline binding uses the .send() method for direct data ingestion.

3. Passthrough Mode (Wrap Another Adapter)

Use the core createPassthroughAdapter utility to send logs to both R2 SQL Pipeline and another logs adapter:

import { createPassthroughAdapter } from "@authhero/adapter-interfaces";
import { createR2SQLLogsAdapter } from "@authhero/cloudflare-adapter";

// Primary adapter (e.g., existing database)
const databaseAdapter = createDatabaseLogsAdapter();

// R2 SQL Pipeline adapter
const r2SqlAdapter = createR2SQLLogsAdapter({
  pipelineEndpoint: "https://your-stream-id.ingest.cloudflare.com",
  authToken: env.R2_SQL_AUTH_TOKEN,
  warehouseName: env.R2_WAREHOUSE_NAME,
});

// Create passthrough adapter
const logsAdapter = createPassthroughAdapter({
  primary: databaseAdapter,
  secondaries: [
    {
      adapter: { create: r2SqlAdapter.create },
      onError: (err) => console.error("R2 SQL sync failed:", err),
    },
  ],
});

In passthrough mode:

  • create() calls the primary adapter first, then sends to secondaries in the background
  • get() and list() read from the primary adapter only
  • Secondary errors are logged but don't fail the operation

Methods

  • create(tenantId, log) - Create a log entry (sends to Pipeline)
  • get(tenantId, logId) - Get a log entry (queries R2 SQL)
  • list(tenantId, params) - List logs with pagination and filtering (queries R2 SQL)

Example

// Create a log
const log = await logs.create("tenant-123", {
  type: "s", // Successful login
  date: new Date().toISOString(),
  ip: "192.168.1.100",
  user_agent: "Mozilla/5.0...",
  isMobile: false,
  user_id: "user-456",
  client_id: "app-789",
  description: "User logged in successfully",
});

// List logs with filtering
const result = await logs.list("tenant-123", {
  page: 0,
  per_page: 50,
  include_totals: true,
  sort: {
    sort_by: "date",
    sort_order: "desc",
  },
  q: "user_id:user-456", // Lucene-style filter
});

Querying Logs with R2 SQL

You can query logs directly using the Wrangler CLI:

# Set up authentication
export WRANGLER_R2_SQL_AUTH_TOKEN=your_api_token

# Query logs
npx wrangler r2 sql query "your_warehouse" "
  SELECT * FROM default.logs
  WHERE tenant_id = 'tenant-123'
  AND type = 's'
  ORDER BY date DESC
  LIMIT 100
"

# Count logs by type
npx wrangler r2 sql query "your_warehouse" "
  SELECT type, COUNT(*) as count
  FROM default.logs
  WHERE tenant_id = 'tenant-123'
  GROUP BY type
"

Logs Adapter (Analytics Engine)

Write authentication logs to Cloudflare Workers Analytics Engine for low-latency writes and SQL-based querying.

Architecture

This adapter uses Cloudflare's Workers Analytics Engine:

  • Write: Fire-and-forget writes using writeDataPoint() (no HTTP latency)
  • Query: SQL API for analyzing logs stored in Analytics Engine

When to Use Analytics Engine vs R2 SQL

FeatureAnalytics EngineR2 SQL + Pipelines
Write Latency~0ms (fire-and-forget)~50-100ms (HTTP)
Data Retention90 days (free), configurableUnlimited
Query LanguageSQL (ClickHouse-like)SQL (Iceberg)
Best ForReal-time analytics, recent logsLong-term storage, compliance
PricingFree tier availablePay per storage + queries

Prerequisites

  1. Create an Analytics Engine Dataset:

Configure in wrangler.toml:

[[analytics_engine_datasets]]
binding = "AUTH_LOGS"
dataset = "authhero_logs"
  1. Create an API Token:

Create a Cloudflare API token with Account Analytics: Read permission for querying logs.

Configuration

interface AnalyticsEngineLogsAdapterConfig {
  // Analytics Engine dataset binding (for Workers)
  analyticsEngineBinding?: AnalyticsEngineDataset;

  // Cloudflare account ID (required for SQL queries)
  accountId: string;

  // API token with Analytics Engine read permission
  apiToken: string;

  // Dataset name (default: "authhero_logs")
  dataset?: string;

  // HTTP timeout in ms (default: 30000)
  timeout?: number;
}

Usage

import createAdapters, {
  createAnalyticsEngineLogsAdapter,
} from "@authhero/cloudflare-adapter";

// Option 1: Use via createAdapters
const adapters = createAdapters({
  zoneId: "your-zone-id",
  authKey: "your-api-key",
  authEmail: "your-email",
  customDomainAdapter: yourDbAdapter,

  analyticsEngineLogs: {
    analyticsEngineBinding: env.AUTH_LOGS,
    accountId: env.CLOUDFLARE_ACCOUNT_ID,
    apiToken: env.ANALYTICS_ENGINE_API_TOKEN,
    dataset: "authhero_logs",
  },
});

// Option 2: Use adapter directly
const logsAdapter = createAnalyticsEngineLogsAdapter({
  analyticsEngineBinding: env.AUTH_LOGS,
  accountId: env.CLOUDFLARE_ACCOUNT_ID,
  apiToken: env.ANALYTICS_ENGINE_API_TOKEN,
});

Worker Example

interface Env {
  AUTH_LOGS: AnalyticsEngineDataset;
  CLOUDFLARE_ACCOUNT_ID: string;
  ANALYTICS_ENGINE_API_TOKEN: string;
}

export default {
  async fetch(request: Request, env: Env) {
    const { logs } = createAdapters({
      // ... other config
      analyticsEngineLogs: {
        analyticsEngineBinding: env.AUTH_LOGS,
        accountId: env.CLOUDFLARE_ACCOUNT_ID,
        apiToken: env.ANALYTICS_ENGINE_API_TOKEN,
      },
    });

    // Write a log (fire-and-forget, no await needed)
    await logs.create("tenant-123", {
      type: "s",
      date: new Date().toISOString(),
      ip: request.headers.get("cf-connecting-ip") || "",
      user_agent: request.headers.get("user-agent") || "",
      isMobile: false,
      user_id: "user-456",
      description: "User logged in",
    });

    // Query logs
    const recentLogs = await logs.list("tenant-123", {
      per_page: 50,
      q: "type:s",
    });

    return new Response("OK");
  },
};

Passthrough Mode

Use Analytics Engine alongside another logs adapter using the core createPassthroughAdapter utility:

import { createPassthroughAdapter } from "@authhero/adapter-interfaces";
import { createAnalyticsEngineLogsAdapter } from "@authhero/cloudflare-adapter";

// Primary adapter (e.g., database)
const databaseAdapter = createDatabaseLogsAdapter();

// Analytics Engine adapter for write syncing
const analyticsEngineAdapter = createAnalyticsEngineLogsAdapter({
  analyticsEngineBinding: env.AUTH_LOGS,
  accountId: env.CLOUDFLARE_ACCOUNT_ID,
  apiToken: env.ANALYTICS_ENGINE_API_TOKEN,
});

// Create passthrough adapter - writes to both, reads from primary
const logsAdapter = createPassthroughAdapter({
  primary: databaseAdapter,
  secondaries: [
    {
      adapter: { create: analyticsEngineAdapter.create },
      onError: (err) => console.error("Analytics sync failed:", err),
    },
  ],
});

// logs.create() writes to both adapters (database first, then Analytics Engine)
// logs.get() and logs.list() read from database only

Data Schema

Analytics Engine stores logs using blob and double fields:

FieldTypeDescription
blob1stringlog_id
blob2stringtenant_id
blob3stringtype (e.g., "s", "f")
blob4stringdate (ISO string)
blob5stringdescription
blob6stringip
blob7stringuser_agent
blob8-18stringuser_id, connection, client_id, etc.
blob19-20stringJSON stringified (details, auth0_client)
double1numberisMobile (0 or 1)
double2numbertimestamp (epoch ms)
index1stringtenant_id (for efficient filtering)

Querying with SQL API

# List recent logs for a tenant
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sql" \
  -H "Authorization: Bearer $API_TOKEN" \
  -d "SELECT * FROM authhero_logs WHERE index1 = 'tenant-123' ORDER BY timestamp DESC LIMIT 50"

# Count logins by type
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sql" \
  -H "Authorization: Bearer $API_TOKEN" \
  -d "SELECT blob3 as type, count() as count FROM authhero_logs WHERE index1 = 'tenant-123' GROUP BY blob3"

Outbox Metrics Sink (Analytics Engine)

createAnalyticsEngineOutboxMetricsSink turns authhero's outbox relay metrics into Analytics Engine data points, so dead-letters and retry backoff are queryable instead of only appearing in console output.

Setup

  1. Add a dataset binding in wrangler.toml:
[[analytics_engine_datasets]]
binding = "OUTBOX_METRICS"
dataset = "authhero_outbox_metrics"
  1. Pass the sink to both relay paths — init() (inline per-request delivery) and runOutboxRelay() (the cron drain):
import { createAnalyticsEngineOutboxMetricsSink } from "@authhero/cloudflare-adapter";
import { init, runOutboxRelay } from "authhero";

const metrics = createAnalyticsEngineOutboxMetricsSink({
  analyticsEngineBinding: env.OUTBOX_METRICS,
});

const { app } = init({ dataAdapter, outbox: { enabled: true, metrics } });

// in scheduled():
await runOutboxRelay({ dataAdapter, issuer: env.ISSUER, metrics });

Without analyticsEngineBinding the sink is a silent no-op, so the same wiring works in local dev and tests.

Data Schema

FieldTypeDescription
blob1stringmetric name (outbox_events_processed_total, outbox_events_dead_lettered_total, outbox_retry_delay_seconds)
blob2stringtenant_id
blob3stringevent_type
blob4stringsource (request or cron)
blob5stringdestination (delivery failures only)
blob6stringerror (delivery failures only)
double1numbervalue — 1 for counters, retry delay in seconds for outbox_retry_delay_seconds
double2numberretry_count of the event
double3numbertimestamp (epoch ms)
index1stringtenant_id (for efficient filtering)

Analytics Engine samples rows under load, so weight counters by _sample_interval to get true totals:

# Dead-lettered events per tenant
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sql" \
  -H "Authorization: Bearer $API_TOKEN" \
  -d "SELECT index1 as tenant, sum(double1 * _sample_interval) as dead_lettered FROM authhero_outbox_metrics WHERE blob1 = 'outbox_events_dead_lettered_total' GROUP BY index1"

Geo Adapter

The Cloudflare Geo adapter extracts geographic location information from Cloudflare's automatic request headers. This is used to enrich authentication logs with location data.

Features

  • Zero Latency: Uses headers already provided by Cloudflare
  • No API Calls: No external services or databases required
  • Graceful Degradation: Works with just country code or full location data
  • Free: All features available on Cloudflare's free plan

Cloudflare Setup

The geo adapter requires specific Cloudflare settings to be enabled:

1. Enable IP Geolocation (Required)

This provides the cf-ipcountry header with just the country code.

  1. Go to your Cloudflare dashboard
  2. Navigate to Network settings
  3. Enable IP Geolocation

This provides full location data including city, coordinates, timezone, etc.

  1. Go to your Cloudflare dashboard
  2. Navigate to Rules > Transform Rules > Managed Transforms
  3. Enable Add visitor location headers

This is a free feature available on all Cloudflare plans.

Configuration

The geo adapter is automatically included when you create the Cloudflare adapters. Headers are passed at request time via getGeoInfo(headers):

import createAdapters from "@authhero/cloudflare-adapter";

// Create adapters once at startup
const adapters = createAdapters({
  // ... other config
});

// The geo adapter is always available
// Headers are passed when calling getGeoInfo
const headers = Object.fromEntries(request.headers);
const geoInfo = await adapters.geo.getGeoInfo(headers);

When used with AuthHero, headers are automatically extracted from the Hono context in the logging helper.

Cloudflare Headers Used

HeaderDescriptionExampleAvailability
cf-ipcountry2-letter ISO country codeUSAlways (with IP Geolocation enabled)
cf-ipcityCity nameSan FranciscoWith Managed Transform
cf-iplatitudeLatitude coordinate37.7749With Managed Transform
cf-iplongitudeLongitude coordinate-122.4194With Managed Transform
cf-timezoneIANA timezone identifierAmerica/Los_AngelesWith Managed Transform
cf-ipcontinent2-letter continent codeNAWith Managed Transform

Additional headers available with Managed Transform (not currently mapped):

  • cf-region: Region name
  • cf-region-code: Region code
  • cf-metro-code: Metro code
  • cf-postal-code: Postal code

Response Format

interface GeoInfo {
  country_code: string; // "US" - always available
  city_name: string; // "San Francisco" or "" if not available
  latitude: string; // "37.7749" or "" if not available
  longitude: string; // "-122.4194" or "" if not available
  time_zone: string; // "America/Los_Angeles" or "" if not available
  continent_code: string; // "NA" or "" if not available
}

With only IP Geolocation enabled:

{
  "country_code": "US",
  "city_name": "",
  "latitude": "",
  "longitude": "",
  "time_zone": "",
  "continent_code": ""
}

With "Add visitor location headers" Managed Transform enabled:

{
  "country_code": "US",
  "city_name": "San Francisco",
  "latitude": "37.7749",
  "longitude": "-122.4194",
  "time_zone": "America/Los_Angeles",
  "continent_code": "NA"
}

Integration with AuthHero

When configured in AuthHero, the geo adapter automatically enriches authentication logs. The logging helper extracts headers from the Hono context automatically:

import createAdapters from "@authhero/cloudflare-adapter";

const cloudflareAdapters = createAdapters({
  // ... other config
});

const dataAdapter = {
  ...yourDatabaseAdapter,
  geo: cloudflareAdapters.geo, // Add geo adapter
};

Logs will automatically include location_info:

{
  "type": "s",
  "date": "2025-11-28T12:00:00.000Z",
  "location_info": {
    "country_code": "US",
    "city_name": "San Francisco",
    "latitude": "37.7749",
    "longitude": "-122.4194",
    "time_zone": "America/Los_Angeles",
    "continent_code": "NA"
  }
}

Alternative: IP Geolocation Databases

If you're not using Cloudflare or need more detailed location data, you can implement a custom GeoAdapter using IP geolocation databases like MaxMind GeoIP2:

import maxmind from "maxmind";
import { GeoAdapter, GeoInfo } from "@authhero/adapter-interfaces";

class MaxMindGeoAdapter implements GeoAdapter {
  private reader: maxmind.Reader<maxmind.CityResponse>;

  private constructor(reader: maxmind.Reader<maxmind.CityResponse>) {
    this.reader = reader;
  }

  static async create(databasePath: string): Promise<MaxMindGeoAdapter> {
    const reader = await maxmind.open<maxmind.CityResponse>(databasePath);
    return new MaxMindGeoAdapter(reader);
  }

  async getGeoInfo(headers: Record<string, string>): Promise<GeoInfo | null> {
    // Extract IP from headers (e.g., x-forwarded-for, cf-connecting-ip)
    const ip =
      headers["cf-connecting-ip"] ||
      headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
      headers["x-real-ip"];

    if (!ip) return null;

    const lookup = this.reader.get(ip);

    if (!lookup) return null;

    return {
      country_code: lookup.country?.iso_code || "",
      city_name: lookup.city?.names?.en || "",
      latitude: lookup.location?.latitude?.toString() || "",
      longitude: lookup.location?.longitude?.toString() || "",
      time_zone: lookup.location?.time_zone || "",
      continent_code: lookup.continent?.code || "",
    };
  }
}

// Usage:
const geoAdapter = await MaxMindGeoAdapter.create(
  "/path/to/GeoLite2-City.mmdb",
);

Considerations for IP Databases:

  • Requires database downloads and regular updates
  • Additional latency for lookups (1-5ms typically)
  • May require licensing fees
  • Works in any environment, not just edge platforms

Environment Variables

Recommended environment variables:

# Custom Domains
CLOUDFLARE_ZONE_ID=your_zone_id
CLOUDFLARE_AUTH_KEY=your_api_key
CLOUDFLARE_AUTH_EMAIL=your_email

# R2 SQL Logs (optional)
PIPELINE_ENDPOINT=https://your-stream-id.ingest.cloudflare.com
R2_SQL_AUTH_TOKEN=your_r2_sql_token
R2_WAREHOUSE_NAME=your_warehouse_name

TypeScript

The package includes TypeScript definitions. Import types as needed:

import type {
  CloudflareConfig,
  CloudflareAdapters,
  R2SQLLogsAdapterConfig,
} from "@authhero/cloudflare-adapter";

Code Executors

For user-authored code hooks (Actions), @authhero/cloudflare-adapter ships two code executors. Pick one based on how you want user code deployed:

WorkerLoaderCodeExecutor — Dynamic Workers

Spins up isolated workers on demand from in-memory code via the Worker Loader binding. No separate deploy step — the code stored in your actions/hookCode table is loaded into a fresh isolate per hook invocation (with a hash-based cache so the same code stays warm).

import { WorkerLoaderCodeExecutor } from "@authhero/cloudflare-adapter";
import { init } from "authhero";

// Requires a worker_loaders binding in wrangler.toml:
// [[worker_loaders]]
// binding = "LOADER"

const codeExecutor = new WorkerLoaderCodeExecutor({
  loader: env.LOADER,
});

const { app } = init({ dataAdapter, codeExecutor });

DispatchNamespaceCodeExecutor — Workers for Platforms

Invokes user workers that have been pre-deployed as separate scripts in a dispatch namespace. The executor's deploy() method uploads a script via the Cloudflare API; execute() invokes it via the namespace binding.

import { DispatchNamespaceCodeExecutor } from "@authhero/cloudflare-adapter";
import { init } from "authhero";

// Requires a dispatch_namespaces binding in wrangler.toml:
// [[dispatch_namespaces]]
// binding = "DISPATCHER"
// namespace = "authhero-hooks"

const codeExecutor = new DispatchNamespaceCodeExecutor({
  accountId: env.CF_ACCOUNT_ID,
  apiToken: env.CF_API_TOKEN,
  dispatchNamespace: "authhero-hooks",
  dispatcher: env.DISPATCHER,
});

const { app } = init({ dataAdapter, codeExecutor });

Previously exported as CloudflareCodeExecutor from the authhero and @authhero/cloudflare-adapter packages. That name is still re-exported as a deprecated alias of DispatchNamespaceCodeExecutor and will be removed in the next major.

See the Hooks Guide for details.

License

MIT

版本与发布记录

@authhero/cloudflare-adapter 共发布 255 个版本,首次发布于 2025/03/04;过去 12 个月发布了 204 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
4.2.0latest2026/11/054 天前
4.1.22026/11/045 天前
4.1.12026/11/036 天前
4.1.02026/10/3010 天前
4.0.02026/10/3011 天前
3.4.02026/10/2911 天前

关于 @authhero/cloudflare-adapter 的常见问题

@authhero/cloudflare-adapter 是什么?

Cloudflare-specific adapters for AuthHero, providing integrations with Cloudflare services.

如何安装 @authhero/cloudflare-adapter?

在项目目录运行 npm install @authhero/cloudflare-adapter,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:hono(^4.12.30)、@hono/zod-openapi(^1.4.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@authhero/cloudflare-adapter 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@authhero/cloudflare-adapter 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@authhero/cloudflare-adapter 可以商用吗?

@authhero/cloudflare-adapter 使用 AGPL-3.0-only 许可。最严格的 Copyleft:除分发外,通过网络向用户提供基于它的服务也需要开源对应源码。闭源 SaaS 使用前务必评估。以上为概括,不构成法律意见,请以许可原文为准。

@authhero/cloudflare-adapter 的下载量有多少?

根据 npm 官方下载统计,@authhero/cloudflare-adapter 在 2026/10/07 – 2026/11/05 这 30 天内被下载 1,716 次,日均约 57 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@authhero/cloudflare-adapter 还在维护吗?

最新版本 4.2.0 发布于 2026/11/05(4 天前);至今共发布 255 个版本,其中过去 12 个月发布了 204 个;首次发布于 2025/03/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。