Skip to content

Architecture

The whole platform groups into five tiers:

  1. Identity service — the Go server, single source of truth.
  2. Shared contract — one TypeScript types package every TS consumer pins to.
  3. Browser SDK — what end-user-facing apps depend on.
  4. Backend SDKs — what API services depend on to validate tokens.
  5. Reference — the demo site that exercises every browser-SDK surface live.

Package dependency map

flowchart TB
  subgraph IDENTITY ["Identity service — single source of truth"]
      direction TB
      SERVER["<b>auth-server</b><br/>Go · NestMux<br/>Postgres · Redis<br/><i>new-auth.vendidit.com</i>"]:::server
  end

  subgraph SHARED ["Shared contract"]
      direction TB
      TSSHARED["<b>auth-shared</b><br/>TS types only · source-only<br/>JWT claims · DTOs · errors · constants"]:::shared
  end

  subgraph BROWSER ["Browser SDK family"]
      direction TB
      CLIENT["<b>auth-client</b><br/>TS browser SDK · vanilla core<br/>session manager · flows · transport"]:::client

      subgraph CLIENTADAPTERS ["framework adapters (subpath exports)"]
          direction LR
          REACT["./react"]:::adapter
          PREACT["./preact"]:::adapter
          SOLID["./solid"]:::adapter
          VUE["./vue"]:::adapter
          ASTRO["./astro"]:::adapter
      end

      subgraph CLIENTUI ["UI components"]
          direction LR
          ATOMS["./preact/ui/atoms<br/><i>13 primitives</i>"]:::adapter
          FORMS["./preact/ui/forms<br/><i>25 forms</i>"]:::adapter
          FLOWS["./preact/ui/flows<br/><i>7 end-to-end</i>"]:::adapter
      end

      CLIENT --> CLIENTADAPTERS
      CLIENT --> CLIENTUI
  end

  subgraph TSBACKEND ["TS backend SDK family"]
      direction TB
      TSCORE["<b>auth-server-ts</b><br/>TS vanilla core<br/>AuthClient · Flows · JwtValidator · HttpTransport · ErrorMapper"]:::core
      NEST["<b>auth-server-nest</b><br/>NestJS adapter<br/>module · guards · decorators · providers"]:::adapter

      NEST -->|"wraps"| TSCORE
  end

  subgraph PHPBACKEND ["PHP backend SDK family"]
      direction TB
      PHPCORE["<b>auth-server-php</b><br/>Vanilla PHP 8.1+ core<br/>AuthClient · Flows · JwtValidator · HttpTransport · ErrorMapper"]:::core
      LARAVEL["<b>auth-server-laravel</b><br/>Laravel adapter<br/>facade · middleware · guard · Blade · service provider"]:::adapter

      LARAVEL -->|"wraps"| PHPCORE
  end

  subgraph DEMO ["Reference"]
      DEMOAPP["<b>auth-client-demo</b><br/>Preact · Vite<br/>45 catalog pages + live auth<br/><i>auth-demo.vendidit.com</i>"]:::demo
  end

  CLIENT -.->|"HTTP / REST"| SERVER
  TSCORE -.->|"HTTP / REST"| SERVER
  PHPCORE -.->|"HTTP / REST"| SERVER

  SERVER -.->|"JWT_ACCESS_SECRET"| TSCORE
  SERVER -.->|"JWT_ACCESS_SECRET"| PHPCORE

  CLIENT -.->|"types"| TSSHARED
  TSCORE -.->|"types"| TSSHARED
  NEST -.->|"types"| TSSHARED

  DEMOAPP ==>|"npm dep"| CLIENT

  classDef server fill:#2d4a2b,stroke:#5b8c4a,stroke-width:3px,color:#e8eaf0
  classDef shared fill:#3a3055,stroke:#7a5db5,stroke-width:2px,color:#e8eaf0
  classDef client fill:#1e3a5f,stroke:#5b8def,stroke-width:2px,color:#e8eaf0
  classDef core fill:#1e3a5f,stroke:#5b8def,stroke-width:2px,color:#e8eaf0
  classDef adapter fill:#2a2f3c,stroke:#9ba3b4,stroke-width:1px,color:#e8eaf0
  classDef demo fill:#3a2f1e,stroke:#f5a524,stroke-width:2px,color:#e8eaf0

Legend

Arrow styleMeaning
===> (bold)Hard package dependency (composer require / npm install).
---> (solid)Build-time language-level dependency (subpath export, type re-export).
-..-> (dashed)Runtime communication — HTTP REST, or shared JWT secret (HS256 local validation, no network).

Tier colours

ColourTier
GreenThe identity service. Owns persistence + secrets + signing.
PurpleShared type contract (auth-shared) — pure types, no runtime.
BlueRuntime cores — auth-client (browser), auth-server-ts (Node), auth-server-php (PHP).
GreyFramework adapters — thin wrappers over a core.
AmberThe demo site — reference integration, not part of the SDK surface.

Two-axis view — runtime × language

flowchart LR
  subgraph BROWSER ["Browser runtime"]
      direction TB
      BC["auth-client<br/>(TS · React · Preact · Solid · Vue · Astro)"]:::client
  end

  subgraph NODE ["Node runtime"]
      direction TB
      NC["auth-server-ts<br/>(TS vanilla)"]:::core
      NN["auth-server-nest<br/>(NestJS adapter)"]:::adapter
  end

  subgraph PHP ["PHP runtime"]
      direction TB
      PC["auth-server-php<br/>(vanilla PHP)"]:::core
      PL["auth-server-laravel<br/>(Laravel adapter)"]:::adapter
  end

  subgraph GO ["Go runtime"]
      direction TB
      AS["auth-server<br/>(the API · the only writer)"]:::server
  end

  BC --> AS
  NC --> AS
  PC --> AS
  NN --> NC
  PL --> PC

  classDef server fill:#2d4a2b,stroke:#5b8c4a,stroke-width:3px,color:#e8eaf0
  classDef client fill:#1e3a5f,stroke:#5b8def,stroke-width:2px,color:#e8eaf0
  classDef core fill:#1e3a5f,stroke:#5b8def,stroke-width:2px,color:#e8eaf0
  classDef adapter fill:#2a2f3c,stroke:#9ba3b4,stroke-width:1px,color:#e8eaf0

The pattern is symmetric across languages: one vanilla core per runtime, one framework adapter on top of it. Adding a new framework (Symfony, Rails, FastAPI…) means writing a new adapter that wraps the existing core — never re-implementing JWT validation or HTTP plumbing.

Trust + secret topology

flowchart LR
  subgraph SECRETS ["Shared secrets"]
      S1["JWT_ACCESS_SECRET<br/>HS256 sign/verify"]
      S2["JWT_REFRESH_SECRET"]
      S3["m2m client_secret"]
  end

  SERVER["auth-server"]:::server -- "signs with" --> S1
  SERVER -- "signs with" --> S2
  SERVER -- "issues" --> S3

  S1 -- "verifies with" --> NEST["auth-server-nest"]:::adapter
  S1 -- "verifies with" --> LARAVEL["auth-server-laravel"]:::adapter
  S1 -- "verifies with" --> TSC["auth-server-ts"]:::core
  S1 -- "verifies with" --> PHPC["auth-server-php"]:::core

  classDef server fill:#2d4a2b,stroke:#5b8c4a,stroke-width:3px,color:#e8eaf0
  classDef core fill:#1e3a5f,stroke:#5b8def,stroke-width:2px,color:#e8eaf0
  classDef adapter fill:#2a2f3c,stroke:#9ba3b4,stroke-width:1px,color:#e8eaf0

Notes:

  • The access secret is shared platform-wide so every consumer can validate signatures locally. The refresh secret never leaves the auth-server — refresh tokens are exchanged at /auth/refresh, never validated locally.
  • Secret rotation is dual-slot: each consumer can hold current plus a previous secret while a rotation is in flight. Validators try current first, fall back to previous on signature mismatch only. See auth-server development for the operator runbook.
  • m2m client_credentials live in the auth-server’s m2m_clients table. Each service that needs to call other services authenticates itself by exchanging its client_id + client_secret for a service-scoped JWT (token_type: "service").

Where the demo fits

flowchart LR
  DEV(("Integrator<br/>adding auth to a new app"))
  DEMO["<b>auth-client-demo</b><br/><i>auth-demo.vendidit.com</i>"]:::demo
  SRC["src/pages/<br/>(LoginPage, AdminUserEditPage, …)"]:::demo
  PKG["@vendidit/auth-client/preact/ui/atoms,forms,flows"]:::adapter

  DEV -- "1. browse" --> DEMO
  DEV -- "2. copy pattern" --> SRC
  DEV -- "3. install + integrate" --> PKG

  DEMO -- "every catalog page exercises" --> PKG
  SRC -- "shows the real integration of" --> PKG

  classDef demo fill:#3a2f1e,stroke:#f5a524,stroke-width:2px,color:#e8eaf0
  classDef adapter fill:#2a2f3c,stroke:#9ba3b4,stroke-width:1px,color:#e8eaf0

The demo site is dogfood: every public component the browser SDK ships has a live page on it, plus the real /login, /register, /profile, /admin/users, /admin/audit-log routes that consume the auth-server the same way a production app would. When adding auth to a new app, the shortest path is to find the equivalent demo page and copy the pattern.