Architecture
The whole platform groups into five tiers:
- Identity service — the Go server, single source of truth.
- Shared contract — one TypeScript types package every TS consumer pins to.
- Browser SDK — what end-user-facing apps depend on.
- Backend SDKs — what API services depend on to validate tokens.
- 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 style | Meaning |
|---|---|
===> (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
| Colour | Tier |
|---|---|
| Green | The identity service. Owns persistence + secrets + signing. |
| Purple | Shared type contract (auth-shared) — pure types, no runtime. |
| Blue | Runtime cores — auth-client (browser), auth-server-ts (Node), auth-server-php (PHP). |
| Grey | Framework adapters — thin wrappers over a core. |
| Amber | The 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
currentplus aprevioussecret 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_credentialslive in the auth-server’sm2m_clientstable. Each service that needs to call other services authenticates itself by exchanging itsclient_id+client_secretfor 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.