Skip to content

Architecture

Authrim is a Unified Identity & Access Platform built entirely on the Cloudflare Workers ecosystem. This page covers the core technical architecture: the multi-worker system, database abstraction layer, PII partition routing, and Durable Object region sharding.

System Overview

Authrim is composed of multiple specialized Workers connected via Service Bindings. The ar-router Worker acts as the central entry point, dispatching requests to domain-specific Workers.

flowchart LR
    router["ar-router
(entry point)"] subgraph endpoints["OIDC / Auth"] discovery["ar-discovery
(OIDC meta)"] auth["ar-auth
(AuthZ EP)"] token["ar-token
(Token EP)"] userinfo["ar-userinfo
(UserInfo EP)"] end subgraph federation["Federation"] saml["ar-saml
(SAML IdP)"] bridge["ar-bridge
(External IdP)"] end subgraph ops["Operations"] mgmt["ar-management
(Admin API)"] async["ar-async
(Background)"] end router --> discovery & auth & token & userinfo router --> saml & bridge router --> mgmt & async
WorkerResponsibility
ar-routerRequest routing, rate limiting, CORS
ar-discovery/.well-known/openid-configuration, JWKS
ar-authAuthorization endpoint, consent, login flows
ar-tokenToken endpoint (code exchange, refresh, device)
ar-userinfoUserInfo endpoint
ar-managementAdmin API (users, clients, roles, policies)
ar-samlSAML IdP and SP
ar-bridgeExternal IdP federation (social login, enterprise SSO)
ar-asyncBackground jobs (key rotation, cleanup, SCIM sync)

All Workers share a common library ar-lib-core which provides the database abstraction, repositories, utilities, and Durable Object definitions.

Database Abstraction Layer

DatabaseAdapter Interface

All database operations go through the DatabaseAdapter interface, which abstracts the underlying storage engine:

interface DatabaseAdapter {
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
queryOne<T>(sql: string, params?: unknown[]): Promise<T | null>;
execute(sql: string, params?: unknown[]): Promise<ExecuteResult>;
transaction<T>(fn: (tx: TransactionContext) => Promise<T>): Promise<T>;
batch(statements: PreparedStatement[]): Promise<ExecuteResult[]>;
isHealthy(): Promise<HealthStatus>;
}

The primary implementation is D1Adapter for Cloudflare D1 (serverless SQLite). The adapter includes retry logic with exponential backoff and health check monitoring.

Transaction semantics: D1 does not support traditional SQL transactions. Instead, the D1Adapter collects all statements within a transaction() call and executes them as a D1 batch — providing all-or-nothing semantics.

BaseRepository Pattern

All entity repositories extend BaseRepository<T>, which provides:

  • CRUD operationsfindById, create, update, delete
  • Pagination — cursor-based with configurable sort
  • Filtering — type-safe conditions with operator support (eq, in, like, etc.)
  • Soft delete — via is_active flag (default behavior)
  • SQL injection prevention — field name validation against an allowlist

Three-Database Separation

Authrim uses three separate D1 databases to isolate data by sensitivity:

DatabasePurposeContent
DB_COREAuthentication coreUsers (non-PII), clients, sessions, tokens, roles
DB_PIIPersonal dataEmail, name, address — partitioned by geography
DB_ADMINPlatform managementAdmin users, audit logs, tenant settings

This separation ensures PII can be stored in a jurisdiction-appropriate database while authentication operations only touch DB_CORE.

PII Partition Router

The PIIPartitionRouter routes PII data access to the correct database partition. Each partition maps to a separate DatabaseAdapter instance (potentially in different geographic regions).

Trust Level Hierarchy

When determining which partition to store a new user’s PII, the router evaluates a trust hierarchy (highest trust first):

PriorityMethodTrust LevelDescription
1Tenant policyHighTenant-specific partition override
2Declared residenceHighUser’s self-declared country of residence
3Custom rulesMediumAttribute-based routing rules (plan, role, etc.)
4IP routingLowCloudflare geo headers (fallback only)
5Default partitionLast resort

Partition Configuration

Partition settings are stored in KV (with in-memory caching, 10s TTL) and configurable per tenant via the Admin API:

  • Available partitions — registered database adapters (e.g., eu, us, apac, default)
  • Tenant overrides — force all users of a tenant to a specific partition
  • Custom rules — attribute-based conditions with priority ordering
  • IP routing toggle — enable/disable geographic fallback

The users_core.pii_partition column tracks which partition contains each user’s PII, enabling correct routing for subsequent reads.

Durable Object Region Sharding

Durable Objects (DOs) provide strongly consistent, stateful storage — used in Authrim for sessions, authorization codes, challenges, refresh tokens, and more. Region sharding distributes these DOs across multiple shards and geographic regions.

Why Region Sharding?

A single Durable Object instance can handle approximately 50-100 requests/second. For a platform serving thousands of concurrent authentication flows, a single DO per resource type would become a bottleneck. Region sharding solves this by:

  1. Horizontal scaling — distributing load across N shards
  2. Geographic locality — placing DOs near users via locationHint
  3. Predictable routing — embedding shard info in resource IDs for zero-lookup routing

Shard Configuration

Shard Key Algorithm

Authrim uses FNV-1a (32-bit) hash to determine shard assignment:

shardIndex = fnv1a32(shardKey) % totalShards

The shardKey varies by resource type:

  • Session: random secure ID (uniform distribution)
  • AuthCode / RefreshToken: userId:clientId (colocated by user-client pair)
  • PAR / DeviceCode / CIBA / DPoP: clientId
  • Challenge: random ID

Region ID Format

Every region-sharded resource ID embeds routing information:

g{generation}:{region}:{shard}:{prefix}_{randomPart}

Examples:

  • g1:apac:3:ses_X7g9kPq2Lm4R — Session in APAC, shard 3, generation 1
  • g1:enam:1:acd_9f8a2b1c — AuthCode in US East, shard 1

The corresponding DO instance name follows:

{tenantId}:{region}:{typeAbbrev}:{shard}

Example: default:apac:ses:3

Region Distribution

Shards are divided among geographic regions based on a percentage distribution. The default configuration (4 total shards):

RegionPercentageShardsRange
enam (US East)50%20–1
weur (West Europe)25%12
apac (Asia Pacific)25%13

The calculateRegionRanges() function converts percentages to concrete shard ranges, ensuring all shards are covered.

Placement and Colocation

locationHint Placement

When creating a DO stub, Authrim passes locationHint to Cloudflare:

namespace.get(id, { locationHint: 'apac' });

This hint is only effective on the first get() call for a given DO ID — it determines where Cloudflare physically places the DO. Subsequent calls route to the already-placed instance.

Colocation Groups

DOs that must route the same shard key to the same shard must have identical shard counts. Authrim defines colocation groups to enforce this:

GroupShard CountMembersReason
user-client4AuthCode, RefreshTokenSame userId:clientId key
random-high-rps4RevocationHigh throughput
random-medium-rps4Session, ChallengeMedium throughput
client-based4PAR, DeviceCode, CIBA, DPoPSame clientId key
vc4CredOffer, VPRequestVerifiable Credentials

Mismatched shard counts within a colocation group cause intermittent authentication failures — a user’s AuthCode and RefreshToken would land on different shards, breaking the code exchange flow.

Migration and Routing

Generation-Based Migration

When shard configuration changes (e.g., scaling from 4 to 32 shards), Authrim uses a generation-based approach:

  1. The current generation config is archived to previousGenerations
  2. A new generation is created with updated shard count and distribution
  3. New resources use the new generation
  4. Existing resources continue routing to their original generation (info is embedded in their ID)

This means no data migration is required — old and new resources coexist with different shard configurations. Up to 5 previous generations are retained.

Resource Creation Flow

1. Generate random ID / compute shard key
2. Get RegionShardConfig from KV (cached 10s)
3. Calculate: shardIndex = fnv1a32(shardKey) % totalShards
4. Resolve region from shard ranges
5. Create resource ID: g{gen}:{region}:{shard}:{prefix}_{random}
6. Build DO instance name: {tenant}:{region}:{type}:{shard}
7. Get DO stub with locationHint
8. Send request to DO

Existing Resource Access Flow

1. Parse resource ID → extract generation, region, shard
2. Build DO instance name from parsed info
3. Get DO stub with locationHint (routes to existing placement)
4. Send request to DO

No configuration lookup is needed for existing resources — all routing information is embedded in the ID itself.

Caching Strategy

Authrim uses a three-tier caching strategy to minimize database reads:

block-beta
    columns 1
    kv["KV Cache (global, ~60s)
JWKS, OIDC metadata, settings"] do["DO In-Memory (per-shard)
Sessions, tokens, codes"] d1["D1 Database (persistent)
Source of truth"]
  • KV: Global key-value store with eventual consistency. Used for configuration, public keys, and read-heavy data.
  • DO in-memory: Each Durable Object maintains in-memory state. Provides strongly consistent reads within a shard.
  • D1: The persistent store and source of truth. All writes go to D1; reads are served from cache when possible.

Configuration caches (region shard config, partition settings) use a 10-second TTL to balance freshness with performance.

Next Steps