@iqauth/sdk 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +384 -181
  2. package/dist/browser-session.d.mts +1 -1
  3. package/dist/browser-session.d.ts +1 -1
  4. package/dist/browser-session.js +4 -1
  5. package/dist/browser-session.mjs +1 -1
  6. package/dist/{chunk-JQWYIIIS.mjs → chunk-MDUHPQMM.mjs} +4 -1
  7. package/dist/{chunk-73R6BEGO.mjs → chunk-ZESHDJDU.mjs} +1 -1
  8. package/dist/{client-CggvJmmm.d.ts → client-DXbHb2ul.d.ts} +1 -1
  9. package/dist/{client-C1DXfB8Z.d.mts → client-Dv4v92Mj.d.mts} +1 -1
  10. package/dist/{express-BKAXB5Nl.d.ts → express-B4o3P8vK.d.ts} +1 -1
  11. package/dist/{express-CpfyYTmw.d.mts → express-BZmF1llh.d.mts} +1 -1
  12. package/dist/express.d.mts +3 -3
  13. package/dist/express.d.ts +3 -3
  14. package/dist/express.js +4 -1
  15. package/dist/express.mjs +2 -2
  16. package/dist/fastify.d.mts +6 -0
  17. package/dist/fastify.d.ts +6 -0
  18. package/dist/fastify.js +21 -3
  19. package/dist/fastify.mjs +18 -3
  20. package/dist/hono.js +4 -1
  21. package/dist/hono.mjs +1 -1
  22. package/dist/index.d.mts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +4 -1
  25. package/dist/index.mjs +2 -2
  26. package/dist/mobile.d.mts +1 -1
  27. package/dist/mobile.d.ts +1 -1
  28. package/dist/mobile.js +4 -1
  29. package/dist/mobile.mjs +1 -1
  30. package/dist/next.js +4 -1
  31. package/dist/next.mjs +1 -1
  32. package/dist/react.d.mts +7 -0
  33. package/dist/react.d.ts +7 -0
  34. package/dist/react.js +147 -33
  35. package/dist/react.mjs +147 -33
  36. package/dist/server.d.mts +2 -2
  37. package/dist/server.d.ts +2 -2
  38. package/dist/server.js +4 -1
  39. package/dist/server.mjs +2 -2
  40. package/dist/service.d.mts +1 -1
  41. package/dist/service.d.ts +1 -1
  42. package/dist/service.js +4 -1
  43. package/dist/service.mjs +1 -1
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -1,287 +1,490 @@
1
1
  # @iqauth/sdk
2
2
 
3
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
4
- [![License](https://img.shields.io/badge/license-proprietary-lightgrey.svg)]()
5
- [![Module Format](https://img.shields.io/badge/format-CJS%20%2B%20ESM-blue.svg)]()
6
- [![Docs](https://img.shields.io/badge/docs-environment--specific-blue.svg)](./docs/)
3
+ [![npm](https://img.shields.io/npm/v/@iqauth/sdk.svg)](https://www.npmjs.com/package/@iqauth/sdk)
4
+ [![Node](https://img.shields.io/node/v/@iqauth/sdk.svg)](https://nodejs.org)
5
+ [![Types](https://img.shields.io/npm/types/@iqauth/sdk.svg)](https://www.typescriptlang.org/)
6
+ [![Module Format](https://img.shields.io/badge/format-CJS%20%2B%20ESM-blue.svg)](https://www.npmjs.com/package/@iqauth/sdk)
7
+ [![License](https://img.shields.io/badge/license-proprietary-lightgrey.svg)](#license)
7
8
 
8
- Type-safe TypeScript SDK for IQAuthService.
9
+ The canonical TypeScript SDK for **IQAuthService** — DispositionIQ's multi-tenant identity and authorization platform. One package covers the React client, the four major Node frameworks (Express, Fastify, Hono, Next.js), native mobile, and headless service automation.
10
+
11
+ > **New in 2.0.3** — `SessionManager` gains `serverManagedSession: true` for confidential-client apps, and the cookie-managed `/refresh` helper no longer wipes cookies on transient failures. See [What's new in 2.0.3](#whats-new-in-203).
12
+
13
+ ---
14
+
15
+ ## Table of contents
16
+
17
+ - [Install](#install)
18
+ - [Five-line integration](#five-line-integration)
19
+ - [Pick your environment](#pick-your-environment)
20
+ - [What's new in 2.0.3](#whats-new-in-203)
21
+ - [Browser apps with a backend (recommended)](#browser-apps-with-a-backend-recommended)
22
+ - [Server reference (Express, Fastify, Hono, Next.js)](#server-reference)
23
+ - [Token verification without a framework adapter](#token-verification-without-a-framework-adapter)
24
+ - [Native mobile (PKCE)](#native-mobile-pkce)
25
+ - [Service automation / API keys](#service-automation--api-keys)
26
+ - [Hosted auth pages and branding](#hosted-auth-pages-and-branding)
27
+ - [CLI](#cli)
28
+ - [Error handling](#error-handling)
29
+ - [Troubleshooting](#troubleshooting)
30
+ - [Bundled docs](#bundled-docs)
31
+ - [License](#license)
32
+
33
+ ---
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ npm install @iqauth/sdk
39
+ ```
40
+
41
+ - Node **>= 18** (the SDK uses native `fetch` and Web Crypto).
42
+ - Ships **CJS + ESM + .d.ts** for every entry point.
43
+ - React is an **optional** peer (`>=18`). You only need it if you import from `@iqauth/sdk/react`.
44
+
45
+ You'll need two values from the IQAuth admin dashboard for any app you integrate:
46
+
47
+ - **Publishable key** (`pk_live_…` / `pk_test_…`) — safe to ship to the browser. Self-describes `{iss, appId, tenantId, kid}`, so the SDK auto-discovers your issuer; you almost never need to set it manually.
48
+ - **Secret key** (`sk_live_…` / `sk_test_…`) — server-only. Keep it in env vars, never in client code.
49
+
50
+ Create both in one call from the admin Quickstart wizard, or run `npx iqauth init` (see [CLI](#cli)).
51
+
52
+ ---
9
53
 
10
54
  ## Five-line integration
11
55
 
12
- **React (client):**
56
+ ### React (browser)
57
+
13
58
  ```tsx
14
59
  import { IQAuthProvider, SignedIn, SignedOut, RedirectToSignIn } from "@iqauth/sdk/react";
15
60
 
16
- <IQAuthProvider publishableKey={import.meta.env.VITE_IQAUTH_PUBLISHABLE_KEY}>
17
- <SignedIn><App /></SignedIn>
18
- <SignedOut><RedirectToSignIn /></SignedOut>
19
- </IQAuthProvider>
61
+ export default function App() {
62
+ return (
63
+ <IQAuthProvider publishableKey={import.meta.env.VITE_IQAUTH_PUBLISHABLE_KEY}>
64
+ <SignedIn><Dashboard /></SignedIn>
65
+ <SignedOut><RedirectToSignIn /></SignedOut>
66
+ </IQAuthProvider>
67
+ );
68
+ }
20
69
  ```
21
70
 
22
- **Express / Fastify / Hono / Next.js (server):**
23
- ```js
71
+ Available hooks: `useUser()`, `useSession()`, `useAuth()`, `useOrganization()`. Each returns `{ data, isLoading, error }`.
72
+ Drop-in components: `<SignIn/>`, `<SignUp/>`, `<UserButton/>`, `<UserProfile/>`, `<OrganizationSwitcher/>`, `<AuthCallback/>`.
73
+
74
+ ### Express
75
+
76
+ ```ts
24
77
  import express from "express";
25
- import { iqAuth } from "@iqauth/sdk/express"; // or /fastify, /hono, /next
78
+ import { iqAuth } from "@iqauth/sdk/express";
26
79
 
27
80
  const app = express();
28
81
  app.use(express.json());
29
- const auth = iqAuth({ publishableKey: process.env.IQAUTH_PUBLISHABLE_KEY, secretKey: process.env.IQAUTH_SECRET_KEY });
30
- auth.attachHelpers(app); // mounts /api/iqauth/{callback,refresh,signout} (httpOnly cookies)
31
- app.use(auth); // verifies Bearer or iqauth_at cookie; populates req.auth
32
- ```
33
82
 
34
- The middleware is **cookie-aware** (Bearer header OR `iqauth_at` cookie) and the issuer is **auto-discovered from your publishable key** — no extra config.
83
+ const auth = iqAuth({
84
+ publishableKey: process.env.IQAUTH_PUBLISHABLE_KEY!,
85
+ secretKey: process.env.IQAUTH_SECRET_KEY!,
86
+ });
35
87
 
36
- Working examples live in `examples/{react-vite,express,fastify,hono,next}/`.
88
+ auth.attachHelpers(app); // mounts /api/iqauth/{callback,refresh,signout} (HttpOnly cookies)
89
+ app.use(auth); // verifies Bearer header OR iqauth_at cookie; populates req.auth
90
+ ```
37
91
 
38
- ## CLI
92
+ ### Fastify
39
93
 
40
- ```sh
41
- npx iqauth init # bootstrap a new app + write IQAUTH_* keys to .env
42
- npx iqauth doctor # check .env, issuer reachability, JWKS, redirect URI
43
- npx iqauth keys list --app <id>
44
- npx iqauth keys rotate --app <id> --key-id <id> --yes
45
- npx iqauth dev # run the bundled React example with your key
94
+ ```ts
95
+ import Fastify from "fastify";
96
+ import { iqAuth } from "@iqauth/sdk/fastify";
97
+
98
+ const app = Fastify();
99
+ const auth = iqAuth({ publishableKey: process.env.IQAUTH_PUBLISHABLE_KEY!, secretKey: process.env.IQAUTH_SECRET_KEY! });
100
+ await app.register(auth.plugin); // mounts helpers + decorates request.auth
46
101
  ```
47
102
 
48
- ---
103
+ ### Hono
49
104
 
50
- ## Environment Matrix
105
+ ```ts
106
+ import { Hono } from "hono";
107
+ import { iqAuth } from "@iqauth/sdk/hono";
51
108
 
52
- This package is not one auth model for every environment. The safe integration pattern depends on where credentials live.
109
+ const app = new Hono();
110
+ const auth = iqAuth({ publishableKey: process.env.IQAUTH_PUBLISHABLE_KEY!, secretKey: process.env.IQAUTH_SECRET_KEY! });
111
+ app.route("/api/iqauth", auth.helpers);
112
+ app.use("*", auth.middleware);
113
+ ```
53
114
 
54
- | Environment | Recommended pattern | Refresh token owner | Primary SDK fit |
55
- |--------|--------|--------|--------|
56
- | First-party browser app | Backend proxy + `httpOnly` cookies | Backend only | Resource calls, server verification, shared API modules |
57
- | Native mobile app | Authorization code + PKCE + secure OS storage | Mobile app secure storage | Token-owning client patterns |
58
- | Server-side app | Direct token handling or request-scoped bearer verification | Server | Strong fit |
59
- | Service / automation | API key or dedicated service credentials | Service | Strong fit |
115
+ ### Next.js (App Router)
60
116
 
61
- ## Start Here
117
+ ```ts
118
+ // app/api/iqauth/[...iqauth]/route.ts
119
+ import { iqAuth } from "@iqauth/sdk/next";
120
+ const auth = iqAuth({ publishableKey: process.env.IQAUTH_PUBLISHABLE_KEY!, secretKey: process.env.IQAUTH_SECRET_KEY! });
121
+ export const { GET, POST } = auth.handlers;
122
+ ```
62
123
 
63
- ### First-party browser apps
124
+ ```ts
125
+ // In any Server Component / Route Handler / Server Action
126
+ import { getAuth } from "@iqauth/sdk/next";
127
+ const session = await getAuth(); // null when signed-out
128
+ ```
64
129
 
65
- Do not treat this SDK's token-owning client as the default browser integration path.
130
+ The middleware is **cookie-aware** (Bearer header OR `iqauth_at` cookie) and the issuer is **auto-discovered from your publishable key** — no extra config.
66
131
 
67
- For first-party web applications:
132
+ ---
68
133
 
69
- - proxy login, MFA, tenant selection, refresh, and logout through your backend
70
- - store access and refresh tokens in `httpOnly` cookies
71
- - restore session with a backend endpoint such as `/auth/me`
72
- - do not store refresh tokens in `localStorage`, `sessionStorage`, or browser-readable memory as your durable session model
134
+ ## Pick your environment
73
135
 
74
- The browser should call your backend. Your backend should call IQAuth.
136
+ The SDK is not "one auth model for every environment". The safe pattern depends on where credentials live.
75
137
 
76
- ### Native mobile apps
138
+ | Environment | Recommended pattern | Refresh-token owner | Entry point |
139
+ |---|---|---|---|
140
+ | First-party browser app **with a backend** | Backend proxy + HttpOnly cookies | Backend only | `@iqauth/sdk/{express,fastify,hono,next}` + `@iqauth/sdk/react` (with `serverManagedSession`) |
141
+ | First-party browser app **without a backend** (SPA only) | Authorization-code + PKCE in-browser, refresh in JS-readable cookie | Browser (in cookie) | `@iqauth/sdk/browser` + `@iqauth/sdk/react` |
142
+ | Native mobile | Authorization-code + PKCE, secure OS storage | Mobile app secure storage | `@iqauth/sdk/mobile` |
143
+ | Server-side / API resource server | Bearer verification via JWKS | Server (per request) | `@iqauth/sdk/server` |
144
+ | Service / automation / cron | API key | Service | `@iqauth/sdk/service` |
77
145
 
78
- Use authorization code flow with PKCE:
146
+ > **Rule of thumb:** if your app has its own backend, use a framework adapter and turn on `serverManagedSession: true` in the React provider. Don't store refresh tokens in `localStorage`/`sessionStorage` as your durable session model.
79
147
 
80
- - generate `state`, `nonce`, and PKCE verifier/challenge on device
81
- - complete auth in the system browser
82
- - exchange the authorization code
83
- - store tokens only in Keychain / Keystore / secure OS-backed storage
148
+ ---
84
149
 
85
- ### Server-side apps
150
+ ## What's new in 2.0.3
86
151
 
87
- This SDK is a good fit for:
152
+ ### 1. `serverManagedSession: true` for `SessionManager` / `IQAuthProvider`
88
153
 
89
- - trusted backend services
90
- - Express resource servers
91
- - admin tooling
92
- - request-scoped token handling
93
- - bearer-token verification through JWKS
154
+ For any app whose backend uses one of the framework adapters (`@iqauth/sdk/{express,fastify,hono,next}`), the backend owns the HttpOnly `iqauth_at` + `iqauth_rt` cookies and should be the **sole** authority on token rotation. Opt into that explicitly:
94
155
 
95
- ### Service clients
156
+ ```tsx
157
+ <IQAuthProvider
158
+ publishableKey={import.meta.env.VITE_IQAUTH_PUBLISHABLE_KEY}
159
+ serverManagedSession
160
+ >
161
+
162
+ </IQAuthProvider>
163
+ ```
96
164
 
97
- Prefer API keys or dedicated service credentials over interactive user session flows.
165
+ What changes when this is on:
98
166
 
99
- ## Current SDK Shape
167
+ - `bootstrap()` learns session state with a single read-only `GET /api/v1/auth/me` (override with `userinfoPath`) instead of POSTing to `/refresh`. **No rotation, no race surface.**
168
+ - The proactive-refresh timer is suppressed — the server middleware refreshes on real navigation, single-flight per request.
169
+ - The browser `tokenStore` defaults to a no-op store, so JS never tries to read the (HttpOnly, invisible) refresh cookie.
100
170
 
101
- The current package surface is still token-oriented. That is acceptable for server, mobile, and service contexts, but it should not be read as a recommendation to store refresh tokens directly in first-party browser JavaScript.
171
+ This eliminates the multi-tab + React StrictMode + proactive-timer race that previously produced silent forced sign-outs.
102
172
 
103
- Use the package today with that distinction in mind:
173
+ ### 2. `clearCookiesOnRefreshFailure: "terminal-only" | "always" | "never"`
104
174
 
105
- - server: recommended
106
- - mobile: acceptable with PKCE + secure storage discipline
107
- - browser first-party: use only behind a backend session boundary
108
- - service: recommended with API keys
175
+ The cookie helper `handleRefresh` (used by every framework adapter) now defaults to `"terminal-only"`. Cookies are only cleared when the issuer signals the session is unrecoverable:
109
176
 
110
- ## Install
177
+ - `TOKEN_REVOKED`, `SESSION_REVOKED`, `INVALID_GRANT`
178
+ - `USER_DEACTIVATED`, `USER_DISABLED`, `TENANT_SUSPENDED`
179
+ - HTTP `410 Gone`
111
180
 
112
- ```bash
113
- npm install @iqauth/sdk
181
+ Transient failures — `TOKEN_INVALID` from a rotated-out token, `TOKEN_EXPIRED`, network blips, 5xx — return 401 with cookies intact, so the next legitimate request can either succeed against a still-valid access cookie or be redirected to sign-in cleanly by the middleware.
182
+
183
+ ```ts
184
+ iqAuth({
185
+ publishableKey, secretKey,
186
+ clearCookiesOnRefreshFailure: "terminal-only", // default; "always" restores pre-2.0.3 behavior
187
+ });
114
188
  ```
115
189
 
116
- Supports both CommonJS and ES Modules. Ships with TypeScript declarations.
190
+ ### 3. Next.js 15+ async cookies
117
191
 
118
- ## Entry Points
192
+ `getAuth()` in `@iqauth/sdk/next` now `await`s `next/headers#cookies()` — fixes the runtime warning and the occasional null-session on the first request after build on Next 15 / 16.
119
193
 
120
- Use the entry point that matches your environment:
194
+ ---
121
195
 
122
- ```typescript
123
- import { createBrowserSessionClient } from "@iqauth/sdk/browser-session";
124
- import { createServerClient, ServerIQAuthClient } from "@iqauth/sdk/server";
125
- import { createMobileClient } from "@iqauth/sdk/mobile";
126
- import { createServiceClient } from "@iqauth/sdk/service";
127
- ```
196
+ ## Browser apps with a backend (recommended)
128
197
 
129
- ## Server Quick Start
198
+ This is the integration path you want for any product with its own server. The browser never sees a refresh token; the backend owns rotation; cookies are HttpOnly.
199
+
200
+ ```tsx
201
+ // client/src/main.tsx
202
+ import { IQAuthProvider, SignedIn, SignedOut, RedirectToSignIn } from "@iqauth/sdk/react";
130
203
 
131
- Use the provided middleware by default for Express resource servers.
204
+ createRoot(document.getElementById("root")!).render(
205
+ <IQAuthProvider
206
+ publishableKey={import.meta.env.VITE_IQAUTH_PUBLISHABLE_KEY}
207
+ serverManagedSession
208
+ >
209
+ <SignedIn><App /></SignedIn>
210
+ <SignedOut><RedirectToSignIn /></SignedOut>
211
+ </IQAuthProvider>
212
+ );
213
+ ```
132
214
 
133
- ```typescript
215
+ ```ts
216
+ // server/index.ts
134
217
  import express from "express";
135
- import { createServerClient } from "@iqauth/sdk/server";
218
+ import { iqAuth } from "@iqauth/sdk/express";
136
219
 
137
- const client = createServerClient({
138
- baseUrl: "https://auth.dispositioniq.com",
220
+ const app = express();
221
+ app.use(express.json());
222
+
223
+ const auth = iqAuth({
224
+ publishableKey: process.env.IQAUTH_PUBLISHABLE_KEY!,
225
+ secretKey: process.env.IQAUTH_SECRET_KEY!,
139
226
  });
227
+ auth.attachHelpers(app); // mounts /api/iqauth/{callback,refresh,signout}
228
+ app.use(auth); // populates req.auth on every request
140
229
 
141
- const app = express();
230
+ app.get("/api/me", (req, res) => res.json(req.auth));
231
+ ```
232
+
233
+ That's the whole integration. The hosted sign-in page on `auth.dispositioniq.com` redirects back to `/api/iqauth/callback`, which sets HttpOnly cookies and bounces the user to your `return_to`. Subsequent API calls succeed against the access cookie; on the rare expiry the same cookie endpoint refreshes silently.
234
+
235
+ ### Calling your own API from the browser
236
+
237
+ Use `auth.fetch()` from the React provider — it adds credentials and retries once on 401:
142
238
 
143
- app.use("/api", client.middleware());
239
+ ```tsx
240
+ import { useAuth } from "@iqauth/sdk/react";
144
241
 
145
- app.get("/api/me", (req, res) => {
146
- res.json({
147
- sub: req.auth!.sub,
148
- email: req.auth!.email,
149
- tenantId: req.auth!.tenantId,
150
- roles: req.auth!.roles,
242
+ function ProductList() {
243
+ const { fetch } = useAuth();
244
+ const { data } = useQuery({
245
+ queryKey: ["products"],
246
+ queryFn: () => fetch("/api/products").then(r => r.json()),
151
247
  });
152
- });
248
+ // …
249
+ }
250
+ ```
251
+
252
+ ### Calling **another** IQAuth-protected API (cross-origin)
253
+
254
+ Add the calling app's origin to the target app's `app_allowed_origins` in the admin dashboard. The unified CORS allow-list (per-app origins ∪ global `cors_origins` ∪ `CORS_ORIGINS` env) refreshes within 60 seconds.
255
+
256
+ ---
257
+
258
+ ## Server reference
259
+
260
+ ### Middleware behavior
261
+
262
+ - Accepts `Authorization: Bearer <jwt>` **or** the `iqauth_at` HttpOnly cookie.
263
+ - Verifies RS256 against the issuer's JWKS (cached with stale-while-revalidate).
264
+ - Populates `req.auth` (Express/Fastify), `c.get("auth")` (Hono), or `getAuth()` return value (Next).
265
+ - The shape of `req.auth` is the verified JWT claims plus normalized `{ sub, email, tenantId, roles, permissions, scopes }`.
266
+
267
+ > The SDK uses `req.auth` (not `req.user`) so it never collides with Passport.
268
+
269
+ ### Requiring roles or entitlements
270
+
271
+ ```ts
272
+ app.use("/admin",
273
+ auth.require({
274
+ roles: ["tenant_admin", "platform_admin"],
275
+ entitlements: ["iqcapture"],
276
+ }),
277
+ );
153
278
  ```
154
279
 
155
- If you are building an Express API that accepts bearer tokens, the platform should use the SDK middleware rather than re-implementing token verification manually unless there is a concrete framework constraint.
280
+ `auth.require()` is composable chain it on individual routes, route groups, or globally. Failures throw `IQAuthError` with `code: "INSUFFICIENT_PERMISSIONS"` and the middleware returns 403.
156
281
 
157
- ## Browser Quick Start
282
+ ### Helper routes mounted by `attachHelpers` / `register` / `auth.handlers`
158
283
 
159
- Use a backend proxy. Example shape:
284
+ | Method | Path | Purpose |
285
+ |---|---|---|
286
+ | `GET` | `/api/iqauth/callback` | Receives the OIDC redirect, exchanges the code, sets `iqauth_at` + `iqauth_rt`, redirects to `return_to` |
287
+ | `POST` | `/api/iqauth/refresh` | Reads `iqauth_rt`, rotates, sets new cookies. Honors `clearCookiesOnRefreshFailure` |
288
+ | `POST` | `/api/iqauth/signout` | Revokes the refresh token upstream and clears both cookies |
160
289
 
161
- ```typescript
162
- // frontend
163
- await fetch("/auth/login", {
164
- method: "POST",
165
- credentials: "include",
166
- headers: { "Content-Type": "application/json" },
167
- body: JSON.stringify({ email, password }),
168
- });
290
+ All three set cookies with `HttpOnly; Secure; SameSite=lax; Path=/` by default. Override per-app:
169
291
 
170
- const me = await fetch("/auth/me", {
171
- credentials: "include",
172
- }).then((res) => res.json());
292
+ ```ts
293
+ iqAuth({
294
+ publishableKey, secretKey,
295
+ cookieDomain: ".example.com", // for cross-subdomain SSO
296
+ sameSite: "none", // pair with secure: true
297
+ secure: true,
298
+ cookiePath: "/",
299
+ accessCookieName: "iqauth_at",
300
+ refreshCookieName: "iqauth_rt",
301
+ });
173
302
  ```
174
303
 
175
- ```typescript
176
- // backend
177
- app.post("/auth/login", async (req, res) => {
178
- const result = await iqauth.auth.login(req.body.email, req.body.password);
179
- // set httpOnly cookies here, not localStorage in the browser
304
+ ---
305
+
306
+ ## Token verification without a framework adapter
307
+
308
+ If you're not using Express/Fastify/Hono/Next (custom Node server, AWS Lambda, Cloudflare Worker, etc.):
309
+
310
+ ```ts
311
+ import { createServerClient } from "@iqauth/sdk/server";
312
+
313
+ const client = createServerClient({
314
+ publishableKey: process.env.IQAUTH_PUBLISHABLE_KEY!, // issuer auto-discovered
180
315
  });
316
+
317
+ const claims = await client.tokens.verify(bearerToken);
318
+ // → { sub, email, tenantId, roles, permissions, scopes, iss, aud, exp, … }
181
319
  ```
182
320
 
183
- For the full backend-proxy pattern, see the Fresh Implementation Guide.
321
+ `tokens.verify()` enforces RS256, checks `exp` / `nbf` / `iss` / `aud`, and uses the cached JWKS. Throws `IQAuthError` with codes like `TOKEN_EXPIRED`, `TOKEN_INVALID`, `TOKEN_REVOKED`.
184
322
 
185
- ## Mobile Quick Start
323
+ ---
186
324
 
187
- ```typescript
188
- import { IQAuthClient } from "@iqauth/sdk";
325
+ ## Native mobile (PKCE)
189
326
 
190
- const client = new IQAuthClient({
191
- baseUrl: "https://auth.dispositioniq.com",
192
- accessToken: await secureStore.get("accessToken"),
193
- refreshToken: await secureStore.get("refreshToken"),
194
- onTokenRefresh: async (tokens) => {
195
- await secureStore.set("accessToken", tokens.accessToken);
196
- await secureStore.set("refreshToken", tokens.refreshToken);
327
+ ```ts
328
+ import { createMobileClient } from "@iqauth/sdk/mobile";
329
+
330
+ const client = createMobileClient({
331
+ publishableKey: PUBLISHABLE_KEY,
332
+ redirectUri: "myapp://auth/callback",
333
+ storage: {
334
+ get: (k) => SecureStore.getItemAsync(k),
335
+ set: (k, v) => SecureStore.setItemAsync(k, v),
336
+ delete: (k) => SecureStore.deleteItemAsync(k),
197
337
  },
198
338
  });
339
+
340
+ // 1. begin
341
+ const { url } = await client.signIn.start();
342
+ await WebBrowser.openAuthSessionAsync(url, "myapp://auth/callback");
343
+
344
+ // 2. handle redirect
345
+ await client.signIn.complete(redirectUrl);
346
+
347
+ // 3. use the session
348
+ const me = await client.users.me();
199
349
  ```
200
350
 
201
- This is only appropriate when the tokens are stored in secure OS-backed storage and the login flow uses PKCE.
351
+ Tokens are written to whichever `storage` adapter you provide use Keychain on iOS, Keystore / EncryptedSharedPreferences on Android. Never persist them to AsyncStorage / `localStorage`.
352
+
353
+ ---
354
+
355
+ ## Service automation / API keys
202
356
 
203
- ## API Key Quick Start
357
+ For cron jobs, batch scripts, server-to-server calls — anything that isn't acting on behalf of an interactive user:
204
358
 
205
- ```typescript
206
- import { IQAuthClient } from "@iqauth/sdk";
359
+ ```ts
360
+ import { createServiceClient } from "@iqauth/sdk/service";
207
361
 
208
- const client = new IQAuthClient({
209
- baseUrl: "https://auth.dispositioniq.com",
210
- apiKey: process.env.IQAUTH_API_KEY,
362
+ const client = createServiceClient({
363
+ publishableKey: process.env.IQAUTH_PUBLISHABLE_KEY!,
364
+ apiKey: process.env.IQAUTH_API_KEY!,
211
365
  });
212
366
 
213
- const keys = await client.apiKeys.list();
367
+ const users = await client.users.list({ tenantId: "…" });
368
+ const key = await client.apiKeys.create({ name: "nightly-sync", scopes: ["users:read"] });
214
369
  ```
215
370
 
216
- ## Main Modules
371
+ API-key calls are scoped to the permissions granted at creation time and are subject to per-key rate limits. Rotate with `client.apiKeys.rotate({ id })`; revoke with `client.apiKeys.revoke({ id })`.
217
372
 
218
- | Module | Access | Purpose |
219
- |--------|--------|---------|
220
- | `auth` | `client.auth` | Login, MFA, tenant selection, logout, password reset, OAuth |
221
- | `tokens` | `client.tokens` | JWT verification (RS256/JWKS), decode, expiry checks |
222
- | `sessions` | `client.sessions` | List and revoke sessions |
223
- | `users` | `client.users` | Profile and user management |
224
- | `permissions` | `client.permissions` | Role and entitlement helpers |
225
- | `oidc` | `client.oidc` | Discovery, JWKS, authorization helpers, code exchange |
226
- | `apiKeys` | `client.apiKeys` | API key create/list/revoke/introspect |
227
- | `webhooks` | `client.webhooks` | Endpoint CRUD, deliveries, test, secret rotation |
228
- | `mfa` | `client.mfa` | MFA enrollment, backup codes |
229
- | `branding` | `client.branding` | Branding config CRUD |
373
+ ---
230
374
 
231
- ## Express Middleware
375
+ ## Hosted auth pages and branding
232
376
 
233
- ```typescript
234
- import { createServerClient } from "@iqauth/sdk/server";
377
+ Sign-in, sign-up, forgot-password, and account pages are hosted at `auth.dispositioniq.com`. They render with your app's brand once you set CSS variables in the admin dashboard:
235
378
 
236
- const client = createServerClient({
237
- baseUrl: "https://auth.dispositioniq.com",
238
- });
379
+ - `--brand-primary`
380
+ - `--brand-accent`
381
+ - `--brand-bg`
382
+ - `--brand-surface`
383
+ - `--brand-text`
239
384
 
240
- app.use(
241
- "/admin",
242
- client.middleware({
243
- requiredRoles: ["tenant_admin", "platform_admin"],
244
- requiredEntitlements: ["iqcapture"],
245
- }),
246
- );
385
+ Validate the redirect contract from your own page with `GET /api/public/apps/:appKey/sign-in-context` — only `return_to` origins listed in your app's allowlist will be accepted.
386
+
387
+ If you'd rather embed the same components in your own app shell, the React entry point publishes them directly:
388
+
389
+ ```tsx
390
+ import { SignIn, SignUp, UserButton, UserProfile, OrganizationSwitcher } from "@iqauth/sdk/react";
247
391
  ```
248
392
 
249
- The middleware attaches verified claims to `req.auth` and is the recommended integration path for platforms consuming bearer tokens.
393
+ ---
250
394
 
251
- ## Error Handling
395
+ ## CLI
252
396
 
253
- All API errors throw `IQAuthError`.
397
+ ```sh
398
+ npx iqauth init # bootstrap a new app + write IQAUTH_* keys to .env
399
+ npx iqauth doctor # check .env, issuer reachability, JWKS, redirect URI
400
+ npx iqauth keys list --app <id>
401
+ npx iqauth keys rotate --app <id> --key-id <id> --yes
402
+ npx iqauth keys revoke --app <id> --key-id <id> --yes
403
+ npx iqauth dev # run the bundled React example with your key
404
+ ```
405
+
406
+ `init` is the fastest way to provision a new app end-to-end: it creates the OIDC client, manifest, per-app origin allowlist, and a `pk_/sk_` keypair, then writes them to `.env`. Key rotation has a 24-hour grace window — old and new keys both validate during that window.
407
+
408
+ ---
409
+
410
+ ## Error handling
254
411
 
255
- ```typescript
412
+ Every API failure throws `IQAuthError` with a stable `code`.
413
+
414
+ ```ts
256
415
  import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
257
416
 
258
417
  try {
259
418
  await client.auth.login(email, password);
260
419
  } catch (err) {
261
- if (err instanceof IQAuthError && err.code === ErrorCodes.ACCOUNT_LOCKED) {
262
- // handle lockout
420
+ if (err instanceof IQAuthError) {
421
+ switch (err.code) {
422
+ case ErrorCodes.ACCOUNT_LOCKED: return showLockoutBanner();
423
+ case ErrorCodes.MFA_REQUIRED: return promptMfa(err.details);
424
+ case ErrorCodes.TENANT_SELECTION_REQUIRED: return promptTenant(err.details.tenants);
425
+ case ErrorCodes.INVALID_CREDENTIALS: return showWrongPassword();
426
+ default: return showGenericError(err.message);
427
+ }
263
428
  }
429
+ throw err;
264
430
  }
265
431
  ```
266
432
 
267
- ## Documentation
268
-
269
- - [App Integration Matrix](./docs/APP_INTEGRATION_MATRIX.md)
270
- - [Fresh Implementation Guide](./docs/FRESH_IMPLEMENTATION_GUIDE.md)
271
- - [Browser Session Migration Guide](./docs/BROWSER_SESSION_MIGRATION.md)
272
- - [Tarball Release Workflow](./docs/TARBALL_RELEASE_WORKFLOW.md)
273
- - [Auth Flows](./docs/guides/auth-flows.md)
274
- - [Session Management](./docs/guides/session-management.md)
275
- - [Mobile / Native Guide](./docs/guides/mobile-native.md)
276
- - [Server Platform Integration](./docs/guides/server-platform-integration.md)
277
- - [Service Automation Integration](./docs/guides/service-automation-integration.md)
278
- - [Token Verification](./docs/guides/token-verification.md)
279
- - [API Keys](./docs/guides/api-keys.md)
280
- - [Middleware Reference](./docs/guides/middleware-reference.md)
281
- - [Error Handling](./docs/guides/error-handling.md)
282
- - [V1 to V2 Upgrade Guide](./docs/V1_TO_V2_UPGRADE_GUIDE.md)
283
- - [Integration Prompts](./docs/integration-prompts/)
433
+ Common codes you'll handle:
434
+
435
+ | Code | When |
436
+ |---|---|
437
+ | `INVALID_CREDENTIALS` | Wrong email/password |
438
+ | `ACCOUNT_LOCKED` | Too many failures; show cool-off |
439
+ | `MFA_REQUIRED` | Continue to MFA step with `details.mfaToken` |
440
+ | `TENANT_SELECTION_REQUIRED` | User belongs to >1 tenant; pick one |
441
+ | `TOKEN_EXPIRED` | Access token aged out; refresh handled by SDK helpers |
442
+ | `TOKEN_INVALID` | Token malformed or rotated out (transient under multi-tab) |
443
+ | `TOKEN_REVOKED` / `SESSION_REVOKED` | Terminal — sign the user out |
444
+ | `USER_DEACTIVATED` / `USER_DISABLED` / `TENANT_SUSPENDED` | Terminal — sign out |
445
+ | `INSUFFICIENT_PERMISSIONS` | 403 — caller lacks required role/entitlement |
446
+
447
+ ---
448
+
449
+ ## Troubleshooting
450
+
451
+ **`npx iqauth doctor` is the first stop.** It validates that the `IQAUTH_*` env vars are present and well-formed, the issuer responds, the JWKS endpoint serves keys, and your registered redirect URI matches what the SDK will send.
452
+
453
+ | Symptom | Likely cause |
454
+ |---|---|
455
+ | Silent sign-out within ~60s of login | Pre-2.0.3 cookie-clearing on transient `TOKEN_INVALID`. Upgrade to ≥ 2.0.3, set `serverManagedSession: true` on the React side. |
456
+ | `getAuth()` returns null after first navigation in Next 15+ | Pre-2.0.2 sync-cookies bug. Upgrade to ≥ 2.0.2. |
457
+ | `/api/iqauth/callback` returns 400 "redirect_uri mismatch" | Your registered redirect URI doesn't match the host the browser is on. Add it in the admin app config. |
458
+ | Cross-origin call to a sister IQAuth-protected app gets CORS-blocked | Add the caller's origin to the target app's `app_allowed_origins`. Allow up to 60s for the cache to refresh. |
459
+ | `req.auth` is undefined under Passport | Verify you're reading `req.auth`, not `req.user`. The SDK deliberately uses `req.auth` to avoid Passport collision. |
460
+ | Hosted sign-in page won't accept your `return_to` | The origin isn't in this app's allowlist. Add it in the admin dashboard. |
461
+
462
+ ---
463
+
464
+ ## Bundled docs
465
+
466
+ Long-form integration guides ship **inside the npm tarball** at `node_modules/@iqauth/sdk/docs/`. List them with:
467
+
468
+ ```sh
469
+ ls node_modules/@iqauth/sdk/docs
470
+ ls node_modules/@iqauth/sdk/docs/guides
471
+ ls node_modules/@iqauth/sdk/docs/integration-prompts
472
+ ```
473
+
474
+ Highlights:
475
+
476
+ - `docs/APP_INTEGRATION_MATRIX.md` — which entry point + pattern for which app archetype
477
+ - `docs/FRESH_IMPLEMENTATION_GUIDE.md` — green-field walkthrough for a new app
478
+ - `docs/BROWSER_SESSION_MIGRATION.md` — moving a browser-token-owning app to cookie-managed sessions (now includes the 2.0.3 `serverManagedSession` recipe)
479
+ - `docs/V1_TO_V2_UPGRADE_GUIDE.md` — upgrading from `1.x`
480
+ - `docs/TARBALL_RELEASE_WORKFLOW.md` — internal: shipping prebuilt tarballs to consumer apps
481
+ - `docs/guides/auth-flows.md`, `session-management.md`, `mfa-enrollment.md`, `roles-and-permissions.md`, `scoped-authorization.md`, `entity-hierarchy.md`, `tenant-management.md`, `user-management.md`, `invitations.md`, `branding.md`, `webhooks.md`, `entitlements.md`, `api-keys.md`, `mobile-native.md`, `server-platform-integration.md`, `service-automation-integration.md`, `token-verification.md`, `middleware-reference.md`, `error-handling.md`, `gdpr-compliance.md`, `app-registration.md`
482
+ - `docs/integration-prompts/{first-party-browser-app,native-mobile-app,server-platform-app,service-automation-app,install-from-tarball,migrate-from-local-packages-source}.md` — drop-in prompts for AI-assisted integration
483
+
484
+ These are also kept on the IQAuth admin dashboard's documentation tab.
485
+
486
+ ---
284
487
 
285
488
  ## License
286
489
 
287
- Private — DispositionIQ internal use.
490
+ Proprietary — DispositionIQ internal use. See the LICENSE/usage terms in the bundled docs.