@tktchurch/auth 0.9.1
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.
- package/CHANGELOG.md +210 -0
- package/PUBLISHING.md +121 -0
- package/README.md +315 -0
- package/SECURITY.md +157 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +1 -0
- package/dist/memory-CVWZvsJ0.d.cts +730 -0
- package/dist/memory-CVWZvsJ0.d.ts +730 -0
- package/dist/nuxt/module.cjs +52 -0
- package/dist/nuxt/module.d.cts +12 -0
- package/dist/nuxt/module.d.ts +12 -0
- package/dist/nuxt/module.js +52 -0
- package/dist/storage/memory.cjs +1 -0
- package/dist/storage/memory.d.cts +1 -0
- package/dist/storage/memory.d.ts +1 -0
- package/dist/storage/memory.js +1 -0
- package/package.json +90 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.9.1] - 2026-06-25
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Dual-build pipeline: public npmjs publishes a **consumer** SDK (OAuth/OIDC client
|
|
10
|
+
namespaces only); GitHub Packages publishes the **full** internal SDK with admin
|
|
11
|
+
console modules, `request`/`fetchWithAuth`, and source maps.
|
|
12
|
+
- Public consumer build is minified, ships without source maps, and does not export
|
|
13
|
+
`DEFAULT_BASE_URL` or admin TypeScript types.
|
|
14
|
+
- `package.json` repository metadata now points at `tktchurch/auth-sdk-js`.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `build:consumer`, `build:full`, `release:check:consumer` scripts.
|
|
19
|
+
- `tests/consumer-build.test.ts` guards the public artifact surface.
|
|
20
|
+
|
|
21
|
+
## [0.8.0] - 2026-06-24
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `accounts/` and `developers/` Nuxt server utilities now use `@tktchurch/auth` for
|
|
26
|
+
PKCE authorization start, token exchange/refresh/revoke, user profile fetch, and
|
|
27
|
+
`sanitizePostAuthRedirect`.
|
|
28
|
+
- Privacy UI stub pages: accounts `/account/privacy`, developers `/admin/privacy`
|
|
29
|
+
(hook points for upcoming `auth.privacy.*`).
|
|
30
|
+
|
|
31
|
+
## [0.6.0] - 2026-06-24
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- Platform modules: `families.*`, `dependents.*` (guardian consent), `clients.providers.*`.
|
|
36
|
+
- Client API keys: `clients.apiKeys.list/create/delete` backed by new OAuth
|
|
37
|
+
`GET/POST/DELETE /api/v1/clients/:id/api-keys` routes.
|
|
38
|
+
- OAuth backend admin reads: `GET /api/v1/users/:id/mfa/devices`,
|
|
39
|
+
`GET /api/v1/users/:id/webauthn/credentials`, and `GET /api/v1/sessions?user_id=`
|
|
40
|
+
for console user detail pages.
|
|
41
|
+
|
|
42
|
+
### Tests
|
|
43
|
+
|
|
44
|
+
- Unit coverage in `tests/phase051-platform.test.ts`; extended local-stack and Playwright suites.
|
|
45
|
+
|
|
46
|
+
## [0.5.2] - 2026-06-24
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- `clients.apiKeys.*` SDK methods (see 0.6.0 backend implementation).
|
|
51
|
+
|
|
52
|
+
## [0.5.1] - 2026-06-24
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- Migration parity: `oauth.discoverAuthorizationServer()`, `authorize.probeAuthorize()`,
|
|
57
|
+
`user.uploadAvatar()`, `memberDirectory.lookup()`, `federated.*` URL builders and
|
|
58
|
+
link finalize HTTP methods.
|
|
59
|
+
- Admin reads: `user.admin.getMfaDevices()`, `user.admin.getWebAuthnCredentials()`.
|
|
60
|
+
- List filter wiring: `clients.list({ isActive })`, expanded `UserListQuery`,
|
|
61
|
+
`RoleListQuery`, `SessionListQuery`, `AuditLogListQuery`.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- `webauthn.removeCredential` accepts optional `{ password }` body.
|
|
66
|
+
- `http.ts` supports `FormData` multipart uploads and `redirect: manual`.
|
|
67
|
+
- README: `sessions.revokeOthers` → `revokeAll`.
|
|
68
|
+
|
|
69
|
+
## [0.5.0] - 2026-06-24
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- **Full API parity** with the TKTChurch OAuth backend — user-facing and admin
|
|
74
|
+
console surfaces used by `accounts` / `developers`.
|
|
75
|
+
- User modules: `authorize.consent` / `consentWithCredentials`, `oidc.jwks`,
|
|
76
|
+
`token.tokenExchange` (RFC 8693), `flow.initRecovery` / `executeRecoveryStep`,
|
|
77
|
+
`passwordReset.*`, `mfa.*`, `user` self-service (`updateMe`, `changePassword`,
|
|
78
|
+
recovery contacts, identities), `consents.me.*`, `maintenance.status`,
|
|
79
|
+
`oauth.publicClient`.
|
|
80
|
+
- Admin modules: `clients`, `scopes`, `roles`, `permissions`, `organizations`,
|
|
81
|
+
`auditLogs`, `dashboard`, `jwtKeys`, `webhooks`, `customAttributes`,
|
|
82
|
+
`authMethods`, `authenticationFlows`, `comms` (SMS/email providers, templates,
|
|
83
|
+
logs, test phones), and `user.admin` CRUD.
|
|
84
|
+
- Shared pagination helpers (`normalizePageQuery`, `buildQueryString`,
|
|
85
|
+
`mapPageResponse`) with `MAX_PER_PAGE` cap.
|
|
86
|
+
- Unit tests for all new modules; example smoke tests for PKCE route helpers;
|
|
87
|
+
gated local-stack integration tests (`bun run test:local`).
|
|
88
|
+
|
|
89
|
+
### Changed
|
|
90
|
+
|
|
91
|
+
- `token.revoke` and `token.introspect` now include `client_id` / `client_secret`
|
|
92
|
+
in the request body (required by the OAuth server for RFC 7009 / introspection).
|
|
93
|
+
|
|
94
|
+
### Added (0.5.0 continued)
|
|
95
|
+
|
|
96
|
+
- Expanded local-stack integration tests: PKCE full cycle, OIDC/JWKS, flow init,
|
|
97
|
+
public client metadata.
|
|
98
|
+
- Playwright API e2e suite (`tests/e2e/`) for live backend + security invariants.
|
|
99
|
+
- `bun run test:local:env` and `bun run test:e2e:env` scripts (source `oauth/.env`).
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
### Verified
|
|
103
|
+
|
|
104
|
+
- `bun run release:check` (74 unit tests + 6 gated integration tests)
|
|
105
|
+
|
|
106
|
+
## [0.4.0] - 2026-06-24
|
|
107
|
+
|
|
108
|
+
### Removed (breaking)
|
|
109
|
+
|
|
110
|
+
- `createLocalStorageTokenStorage` and the `@tktchurch/auth/storage/localStorage`
|
|
111
|
+
package subpath. Browser `localStorage` is an XSS sink for OAuth tokens; the
|
|
112
|
+
SDK no longer ships a persistence adapter for it.
|
|
113
|
+
|
|
114
|
+
### Added
|
|
115
|
+
|
|
116
|
+
- Token storage validation exports: `assertValidAuthTokens`, `parseStoredAuthTokens`,
|
|
117
|
+
`serializeAuthTokens`, `validateStorageKey`, `safeParseStoredJson`,
|
|
118
|
+
`DEFAULT_TOKEN_STORAGE_KEY`, `MAX_STORED_TOKEN_BYTES`.
|
|
119
|
+
- `allowInsecureTransport` config flag (default `false`) — rejects non-HTTPS
|
|
120
|
+
`baseUrl` outside loopback hosts.
|
|
121
|
+
- [`SECURITY.md`](./SECURITY.md) threat model and integrator checklist.
|
|
122
|
+
- Hardened examples with PKCE redirect flow, signed OAuth session cookies, safe
|
|
123
|
+
error mapping, and strict input validation ([`examples/`](./examples/)).
|
|
124
|
+
|
|
125
|
+
### Changed
|
|
126
|
+
|
|
127
|
+
- Examples now demonstrate authorization code + PKCE as the primary login path;
|
|
128
|
+
password grant routes are retained for local dev/testing only.
|
|
129
|
+
|
|
130
|
+
### Migration
|
|
131
|
+
|
|
132
|
+
1. Remove `createLocalStorageTokenStorage()` — not replaced.
|
|
133
|
+
2. Use HttpOnly cookies for refresh tokens via server routes (see examples).
|
|
134
|
+
3. Implement custom `TokenStorage` with exported validators for platform-specific
|
|
135
|
+
secure backends (Keychain, Keystore).
|
|
136
|
+
|
|
137
|
+
### Verified
|
|
138
|
+
|
|
139
|
+
- `bun run typecheck`
|
|
140
|
+
- `bun run test`
|
|
141
|
+
- `bun run build`
|
|
142
|
+
|
|
143
|
+
## [0.3.0] - 2026-06-24
|
|
144
|
+
|
|
145
|
+
### Added
|
|
146
|
+
|
|
147
|
+
- **Authorization Code + PKCE redirect flow** (`client.authorize`), the headline
|
|
148
|
+
capability that lets frontends adopt OAuth without re-implementing PKCE/state
|
|
149
|
+
plumbing per app:
|
|
150
|
+
- `authorize.createRequest()` — generates CSPRNG `state` + `nonce` + PKCE
|
|
151
|
+
(`S256` by default) and returns the authorization URL plus the values to
|
|
152
|
+
persist (`state`, `nonce`, `codeVerifier`, `redirectUri`).
|
|
153
|
+
- `authorize.parseCallback()` — normalizes a callback URL/query string.
|
|
154
|
+
- `authorize.handleCallback()` — one call that verifies `state`
|
|
155
|
+
(constant-time), surfaces server errors, and exchanges the code for tokens.
|
|
156
|
+
- Standalone PKCE/CSRF primitives: `generatePkce`, `generateCodeVerifier`,
|
|
157
|
+
`deriveCodeChallenge`, `generateState`, `generateNonce`, `randomUrlSafeString`,
|
|
158
|
+
`timingSafeEqual` — all backed by the platform Web Crypto CSPRNG.
|
|
159
|
+
- `sanitizePostAuthRedirect()` open-redirect guard (rejects protocol-relative,
|
|
160
|
+
backslash-smuggled, scheme-bearing, and CRLF targets).
|
|
161
|
+
- `oidc.discover()` — fetches and maps `/.well-known/openid-configuration`.
|
|
162
|
+
- Broader public type exports (`AuthorizationUrlOptions`, `AuthorizationRequest`,
|
|
163
|
+
`CallbackParams`, `HandleCallbackOptions`, `OidcDiscoveryDocument`, endpoint
|
|
164
|
+
interfaces, `AuthErrorCode`).
|
|
165
|
+
|
|
166
|
+
### Security
|
|
167
|
+
|
|
168
|
+
- All anti-forgery/PKCE randomness uses `globalThis.crypto.getRandomValues`,
|
|
169
|
+
never `Math.random`.
|
|
170
|
+
- `state` is verified with a constant-time comparison (`timingSafeEqual`).
|
|
171
|
+
- PKCE `codeVerifier` is documented as a server-side secret and never appears in
|
|
172
|
+
the authorization URL.
|
|
173
|
+
|
|
174
|
+
### Verified
|
|
175
|
+
|
|
176
|
+
- `bun run typecheck`
|
|
177
|
+
- `bun run test` (includes the RFC 7636 Appendix B S256 vector)
|
|
178
|
+
- `bun run build`
|
|
179
|
+
|
|
180
|
+
## [0.2.0] - 2026-02-26
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- Framework-agnostic `@tktchurch/auth` SDK core with typed OAuth, OIDC, AuthFlow, sessions, user, and WebAuthn endpoint wrappers.
|
|
185
|
+
- Token lifecycle support with pluggable storage and `fetchWithAuth` one-retry refresh flow.
|
|
186
|
+
- Nuxt module export `@tktchurch/auth/nuxt` with:
|
|
187
|
+
- `tktAuth` module configuration.
|
|
188
|
+
- injected `$tktAuth` client via `useTktAuth()`.
|
|
189
|
+
- server factory `createTktServerAuthClient()`.
|
|
190
|
+
- cookie helper `useTktRefreshCookieName()`.
|
|
191
|
+
- Next.js and Nuxt3 production examples in `examples/`.
|
|
192
|
+
- Production-gated Nuxt3 integration tests (`tests/nuxt3.production.test.ts`) for login/me/logout patterns.
|
|
193
|
+
- Release metadata and publish hardening:
|
|
194
|
+
- GitHub Packages publish configuration.
|
|
195
|
+
- npmjs public publish workflow and scripts.
|
|
196
|
+
- release workflow and publishing guide.
|
|
197
|
+
|
|
198
|
+
### Changed
|
|
199
|
+
|
|
200
|
+
- Default SDK endpoint remains `https://prod-auth.tktchurch.com`.
|
|
201
|
+
- Package version bumped to `0.2.0`.
|
|
202
|
+
- Build output now includes the Nuxt module entry (`./nuxt`).
|
|
203
|
+
- Improved strict TypeScript coverage (no implicit `any` in SDK/Nuxt example handlers).
|
|
204
|
+
|
|
205
|
+
### Verified
|
|
206
|
+
|
|
207
|
+
- `bun run typecheck`
|
|
208
|
+
- `bun run test`
|
|
209
|
+
- `bun run build`
|
|
210
|
+
- `bun run test:nuxt3:prod` (passes when target auth server is reachable and seeded credentials are valid)
|
package/PUBLISHING.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Publishing Guide (`@tktchurch/auth`)
|
|
2
|
+
|
|
3
|
+
This package uses a **dual-build** pipeline. The artifact published to each registry differs:
|
|
4
|
+
|
|
5
|
+
| Registry | Build profile | Contents |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| **npmjs** (public) | `consumer` | OAuth/OIDC client SDK only — no admin console modules, no `request`/`fetchWithAuth`, no source maps |
|
|
8
|
+
| **GitHub Packages** (private) | `full` | Complete internal SDK (admin CRUD, privacy admin, generic passthrough, source maps) |
|
|
9
|
+
|
|
10
|
+
Registry selection is command/workflow specific. The repo intentionally does not pin `publishConfig.registry` or a scoped `.npmrc` to avoid cross-registry auth conflicts.
|
|
11
|
+
|
|
12
|
+
## 1. Prerequisites
|
|
13
|
+
|
|
14
|
+
- Repository hosted at `tktchurch/auth-sdk-js`.
|
|
15
|
+
- Package scope is `@tktchurch`.
|
|
16
|
+
- You have permission to publish packages in the `tktchurch` organization.
|
|
17
|
+
- npm scope access for `@tktchurch` (or delegated publish rights).
|
|
18
|
+
- A GitHub token with `write:packages` and `repo` scope (for local GitHub publish).
|
|
19
|
+
- An npm token with publish rights (for local npmjs publish).
|
|
20
|
+
- npm trusted publishing configured for this package/repository (for GitHub Actions npmjs publish).
|
|
21
|
+
|
|
22
|
+
## 2. Local Release Validation
|
|
23
|
+
|
|
24
|
+
Full internal build (used by `accounts` / `developers` via `file:../auth-sdk-js`):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun run release:check
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Consumer build (public npmjs tarball):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun run release:check:consumer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Both run typecheck + tests, then build with the matching profile:
|
|
37
|
+
|
|
38
|
+
| Script | Build |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `bun run build` / `build:full` | `BUILD_PROFILE=full` |
|
|
41
|
+
| `bun run build:consumer` | `BUILD_PROFILE=consumer` |
|
|
42
|
+
|
|
43
|
+
## 3. Local Publish (Manual)
|
|
44
|
+
|
|
45
|
+
### 3.1 Publish to GitHub Packages (private, full SDK)
|
|
46
|
+
|
|
47
|
+
Set auth token:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export NODE_AUTH_TOKEN=ghp_xxx
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Publish:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bun run publish:github
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3.2 Publish to npmjs (public, consumer SDK)
|
|
60
|
+
|
|
61
|
+
Set auth token:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export NODE_AUTH_TOKEN=npm_xxx
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Publish:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bun run publish:npmjs
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Both publish scripts run the matching `release:check*` pipeline and pass `--ignore-scripts` so `prepublishOnly` does not rebuild with the wrong profile.
|
|
74
|
+
|
|
75
|
+
## 4. GitHub Actions Publish
|
|
76
|
+
|
|
77
|
+
Workflow files:
|
|
78
|
+
|
|
79
|
+
1. `.github/workflows/publish-github-packages.yml` — `build:full` → GitHub Packages
|
|
80
|
+
2. `.github/workflows/publish-npmjs.yml` — `build:consumer` → npmjs
|
|
81
|
+
|
|
82
|
+
Triggers:
|
|
83
|
+
|
|
84
|
+
1. Tag push matching `v*` (for example `v0.9.1`)
|
|
85
|
+
2. Manual dispatch
|
|
86
|
+
|
|
87
|
+
The GitHub Packages workflow installs dependencies, typechecks, tests, builds the **full** bundle, and publishes with `GITHUB_TOKEN`.
|
|
88
|
+
|
|
89
|
+
The npmjs workflow installs dependencies, typechecks, tests, builds the **consumer** bundle, and publishes using trusted publishing (GitHub OIDC) when configured.
|
|
90
|
+
|
|
91
|
+
## 5. Tagging and Changelog
|
|
92
|
+
|
|
93
|
+
1. Update `package.json` version.
|
|
94
|
+
2. Update `CHANGELOG.md`.
|
|
95
|
+
3. Create and push a release tag:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git tag v0.9.1
|
|
99
|
+
git push origin v0.9.1
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 6. Required Repository Secrets
|
|
103
|
+
|
|
104
|
+
1. No npmjs token secret is required for npmjs workflow when OIDC trusted publishing is configured.
|
|
105
|
+
2. GitHub Packages workflow uses `GITHUB_TOKEN` automatically.
|
|
106
|
+
|
|
107
|
+
## 7. Consumer vs full SDK
|
|
108
|
+
|
|
109
|
+
**Public npm (`consumer`)** ships:
|
|
110
|
+
|
|
111
|
+
- `authorize`, `token`, `oidc`, `flow`, `user` (self-service), `sessions`, `webauthn`, `passwordReset`, `mfa`, `maintenance.status`, `oauth` meta
|
|
112
|
+
- PKCE, redirect sanitization, token storage helpers
|
|
113
|
+
- Nuxt module entry
|
|
114
|
+
|
|
115
|
+
**GitHub Packages (`full`)** additionally ships:
|
|
116
|
+
|
|
117
|
+
- Admin console namespaces (`clients`, `roles`, `auditLogs`, `dashboard`, …)
|
|
118
|
+
- `request()` and `fetchWithAuth()` passthrough helpers
|
|
119
|
+
- `DEFAULT_BASE_URL` export and full TypeScript admin types
|
|
120
|
+
|
|
121
|
+
Internal TKTChurch apps should install from GitHub Packages or use `file:../auth-sdk-js` with `bun run build:full`.
|
package/README.md
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# `@tktchurch/auth`
|
|
2
|
+
|
|
3
|
+
Framework-agnostic auth SDK for TKTChurch OAuth/AuthFlow services.
|
|
4
|
+
|
|
5
|
+
- Default production endpoint: `https://prod-auth.tktchurch.com`
|
|
6
|
+
- Works in browser and Node runtimes (requires `fetch`)
|
|
7
|
+
- Pluggable token storage (`memory` by default)
|
|
8
|
+
- First-class support for AuthFlow endpoints
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @tktchurch/auth
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createAuthClient } from "@tktchurch/auth";
|
|
20
|
+
|
|
21
|
+
const auth = createAuthClient({
|
|
22
|
+
clientId: "your_client_id",
|
|
23
|
+
// Optional:
|
|
24
|
+
// clientSecret: "your_client_secret",
|
|
25
|
+
// baseUrl: "https://prod-auth.tktchurch.com",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const tokens = await auth.token.password({
|
|
29
|
+
username: "user@example.com",
|
|
30
|
+
password: "your_password",
|
|
31
|
+
scope: "openid profile email",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log(tokens.accessToken);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Authorization Code + PKCE (redirect flow)
|
|
38
|
+
|
|
39
|
+
The recommended browser/SSR login flow. The SDK generates PKCE, `state`, and
|
|
40
|
+
`nonce` for you and hands back everything you must persist across the redirect —
|
|
41
|
+
no more hand-rolling crypto per app.
|
|
42
|
+
|
|
43
|
+
**1. Start the flow** (server route that begins login):
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { createAuthClient } from "@tktchurch/auth";
|
|
47
|
+
|
|
48
|
+
const auth = createAuthClient({ clientId: process.env.TKT_CLIENT_ID! });
|
|
49
|
+
|
|
50
|
+
const request = await auth.authorize.createRequest({
|
|
51
|
+
redirectUri: "https://app.example.com/callback",
|
|
52
|
+
// scope defaults to "openid profile email offline_access"
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Persist these in an HttpOnly cookie / server session, keyed by `state`:
|
|
56
|
+
// request.state, request.nonce, request.codeVerifier, request.redirectUri
|
|
57
|
+
// Then redirect the user agent:
|
|
58
|
+
// 302 -> request.url
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**2. Handle the callback** — one call validates `state` (constant-time),
|
|
62
|
+
surfaces server errors, and exchanges the code for tokens:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const tokens = await auth.authorize.handleCallback({
|
|
66
|
+
callback: requestUrl, // full callback URL or its query string
|
|
67
|
+
expectedState: saved.state, // the state you persisted in step 1
|
|
68
|
+
codeVerifier: saved.codeVerifier,
|
|
69
|
+
redirectUri: saved.redirectUri,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// tokens.accessToken / tokens.refreshToken — persist refresh in HttpOnly cookie;
|
|
73
|
+
// keep access token in server memory per request (see examples/).
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> Security: the PKCE `codeVerifier` is a secret — keep it server-side (HttpOnly
|
|
77
|
+
> cookie or session store), never in `localStorage`. `state` is verified with a
|
|
78
|
+
> constant-time comparison to defeat CSRF and timing oracles.
|
|
79
|
+
|
|
80
|
+
### Open-redirect protection
|
|
81
|
+
|
|
82
|
+
Always sanitize any `next`/`returnTo` value before redirecting after login:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { sanitizePostAuthRedirect } from "@tktchurch/auth";
|
|
86
|
+
|
|
87
|
+
return Response.redirect(sanitizePostAuthRedirect(next, "/dashboard"));
|
|
88
|
+
// "//evil.com", "https://evil.com", "javascript:…", CRLF → fallback
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Low-level PKCE helpers
|
|
92
|
+
|
|
93
|
+
If you need the primitives directly:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { generatePkce, generateState, generateNonce, deriveCodeChallenge } from "@tktchurch/auth";
|
|
97
|
+
|
|
98
|
+
const { codeVerifier, codeChallenge, codeChallengeMethod } = await generatePkce();
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### OIDC discovery
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const meta = await auth.oidc.discover(); // /.well-known/openid-configuration
|
|
105
|
+
console.log(meta.authorizationEndpoint, meta.tokenEndpoint, meta.jwksUri);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Framework-Agnostic Usage
|
|
109
|
+
|
|
110
|
+
### Server route (recommended)
|
|
111
|
+
|
|
112
|
+
Use in-memory storage (the default) and persist refresh tokens in HttpOnly
|
|
113
|
+
cookies. See [`examples/`](./examples/) for full PKCE redirect flows.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { createAuthClient } from "@tktchurch/auth";
|
|
117
|
+
|
|
118
|
+
const auth = createAuthClient({
|
|
119
|
+
clientId: process.env.TKT_CLIENT_ID!,
|
|
120
|
+
clientSecret: process.env.TKT_CLIENT_SECRET!,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export async function GET() {
|
|
124
|
+
const me = await auth.user.me();
|
|
125
|
+
return Response.json(me);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> Do **not** store OAuth tokens in `localStorage` or `sessionStorage`. The SDK
|
|
130
|
+
> does not ship a browser persistence adapter — use server-side cookies or a
|
|
131
|
+
> platform secure store (Keychain, Keystore). See [`SECURITY.md`](./SECURITY.md).
|
|
132
|
+
|
|
133
|
+
## Framework Examples (Best Practices)
|
|
134
|
+
|
|
135
|
+
Production-oriented examples are available in:
|
|
136
|
+
|
|
137
|
+
- [`examples/next-app-router`](./examples/next-app-router/README.md)
|
|
138
|
+
- [`examples/nuxt3`](./examples/nuxt3/README.md)
|
|
139
|
+
|
|
140
|
+
They follow server-first patterns:
|
|
141
|
+
|
|
142
|
+
1. Authorization code + PKCE redirect flow (primary).
|
|
143
|
+
2. `clientSecret` remains server-only.
|
|
144
|
+
3. Refresh token is stored in `HttpOnly` cookie.
|
|
145
|
+
4. PKCE `codeVerifier` is stored in a signed HttpOnly session cookie.
|
|
146
|
+
5. Refresh token rotation is performed on every refresh call.
|
|
147
|
+
6. Logout revokes refresh token and clears cookies.
|
|
148
|
+
|
|
149
|
+
## Nuxt Module (`@tktchurch/auth/nuxt`)
|
|
150
|
+
|
|
151
|
+
Register the module in `nuxt.config.ts`:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
export default defineNuxtConfig({
|
|
155
|
+
modules: ["@tktchurch/auth/nuxt"],
|
|
156
|
+
tktAuth: {
|
|
157
|
+
baseUrl: process.env.TKT_AUTH_BASE_URL ?? "https://prod-auth.tktchurch.com",
|
|
158
|
+
clientId: process.env.TKT_CLIENT_ID,
|
|
159
|
+
clientSecret: process.env.TKT_CLIENT_SECRET,
|
|
160
|
+
autoRefresh: false,
|
|
161
|
+
refreshCookieName: "tkt_refresh_token",
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Then use provided imports:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const auth = createTktServerAuthClient();
|
|
170
|
+
const appAuth = useTktAuth();
|
|
171
|
+
const refreshCookieName = useTktRefreshCookieName();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## AuthFlow Example
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const init = await auth.flow.initAuthentication({
|
|
178
|
+
username: "user@example.com",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
let status = await auth.flow.executeStep({
|
|
182
|
+
sessionId: init.sessionId,
|
|
183
|
+
stepId: init.currentStep.stepId,
|
|
184
|
+
credential: {
|
|
185
|
+
username: "user@example.com",
|
|
186
|
+
password: "secret",
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
while (status.status === "in_progress") {
|
|
191
|
+
// Render UI for status.nextStep and collect credentials for that step.
|
|
192
|
+
status = await auth.flow.executeStep({
|
|
193
|
+
sessionId: status.sessionId,
|
|
194
|
+
stepId: status.nextStep.stepId,
|
|
195
|
+
credential: {},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// status.status === "complete"
|
|
200
|
+
console.log(status.accessToken);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Token Storage
|
|
204
|
+
|
|
205
|
+
### Memory (default)
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { createAuthClient, createMemoryTokenStorage } from "@tktchurch/auth";
|
|
209
|
+
|
|
210
|
+
const auth = createAuthClient({
|
|
211
|
+
clientId: "client_id",
|
|
212
|
+
storage: createMemoryTokenStorage(),
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Custom `TokenStorage`
|
|
217
|
+
|
|
218
|
+
For platform-specific secure backends (Keychain, Keystore, encrypted server
|
|
219
|
+
session), implement `TokenStorage` using the exported validators
|
|
220
|
+
(`assertValidAuthTokens`, `parseStoredAuthTokens`, `serializeAuthTokens`).
|
|
221
|
+
See [`SECURITY.md`](./SECURITY.md).
|
|
222
|
+
|
|
223
|
+
## Error Handling
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { AuthError, MFARequiredAuthError } from "@tktchurch/auth";
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
await auth.token.password({
|
|
230
|
+
username: "user@example.com",
|
|
231
|
+
password: "secret",
|
|
232
|
+
});
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error instanceof MFARequiredAuthError) {
|
|
235
|
+
console.log("MFA required:", error.mfaMethods);
|
|
236
|
+
} else if (error instanceof AuthError) {
|
|
237
|
+
console.log(error.code, error.message, error.status);
|
|
238
|
+
} else {
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Authenticated Fetch
|
|
245
|
+
|
|
246
|
+
`fetchWithAuth` adds the bearer token automatically and retries once on `401` using refresh token when possible.
|
|
247
|
+
|
|
248
|
+
> **Note:** `fetchWithAuth` and `request()` are available only in the **full** SDK (GitHub Packages / local `file:` build). The public npmjs consumer build omits generic passthrough helpers.
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
const response = await auth.fetchWithAuth("/api/v1/users/me");
|
|
252
|
+
if (!response.ok) {
|
|
253
|
+
// handle response
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## API reference (public consumer build)
|
|
258
|
+
|
|
259
|
+
The public npmjs package ships OAuth/OIDC client namespaces only. Admin console modules (`clients`, `roles`, `auditLogs`, …) and generic passthrough helpers are available in the **full** SDK from GitHub Packages — see [`CONTEXT.md`](./CONTEXT.md).
|
|
260
|
+
|
|
261
|
+
| Namespace | Methods | Auth |
|
|
262
|
+
| --- | --- | --- |
|
|
263
|
+
| `token` | `password`, `refresh`, `clientCredentials`, `authorizationCode`, `tokenExchange`, `revoke`, `introspect` | client credentials / stored tokens |
|
|
264
|
+
| `authorize` | `createRequest`, `parseCallback`, `handleCallback`, `consent`, `consentWithCredentials` | public + server session |
|
|
265
|
+
| `oidc` | `userInfo`, `discover`, `jwks` | bearer / public |
|
|
266
|
+
| `oauth` | `publicClient`, `discoverAuthorizationServer` | public |
|
|
267
|
+
| `flow` | `initAuthentication`, `executeStep`, `initRegistration`, `executeRegistrationStep`, `initRecovery`, `executeRecoveryStep`, `getStatus` | public / step session |
|
|
268
|
+
| `user` | `me`, `updateMe`, `changePassword`, `deleteAvatar`, `verifyPhone`, `securityOverview`, `recoveryContacts.*`, `identities.*`, `deleteMe` | bearer |
|
|
269
|
+
| `sessions` | `list`, `current`, `get`, `update`, `revoke`, `revokeAll`, `revokeAllDevices` | bearer |
|
|
270
|
+
| `webauthn` | `registerBegin/Finish`, `authenticateBegin/Finish`, `credentials`, `removeCredential`, `renameCredential`, `setPrimaryCredential` | bearer / public begin |
|
|
271
|
+
| `passwordReset` | `request`, `validate`, `complete` | public |
|
|
272
|
+
| `mfa` | `setup`, `verify`, `devices`, `removeDevice`, `regenerateBackupCodes`, `disable` | bearer |
|
|
273
|
+
| `maintenance` | `status` | public |
|
|
274
|
+
|
|
275
|
+
Integrators must pass an explicit `baseUrl` in `createAuthClient({ baseUrl, clientId, … })`. The public consumer build does not export `DEFAULT_BASE_URL`.
|
|
276
|
+
|
|
277
|
+
### Internal admin SDK (GitHub Packages only)
|
|
278
|
+
|
|
279
|
+
The full build adds `consents`, admin `maintenance.*`, `clients` … `comms`, `privacy`, `user.admin.*`, plus `request()` / `fetchWithAuth()`. Admin methods require a bearer access token with matching backend permissions (`client:read`, `user:write`, `*:*`, etc.). The SDK does not enforce permissions — the OAuth server does.
|
|
280
|
+
|
|
281
|
+
### Migrating from hand-rolled `oauth.ts`
|
|
282
|
+
|
|
283
|
+
Replace direct `fetch` calls with namespaced SDK methods:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
// Before
|
|
287
|
+
await fetch(`${baseUrl}/oauth/token`, { method: "POST", body });
|
|
288
|
+
|
|
289
|
+
// After
|
|
290
|
+
const tokens = await auth.token.authorizationCode({ code, redirectUri, codeVerifier });
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
For admin console CRUD (`/api/v1/clients`, etc.), use the **full** SDK from GitHub Packages — see [`CONTEXT.md`](./CONTEXT.md).
|
|
294
|
+
|
|
295
|
+
Inject internal URL rewriting via `createAuthClient({ fetch })` — same pattern as
|
|
296
|
+
`accounts` / `developers` `oauth-fetch.ts`.
|
|
297
|
+
|
|
298
|
+
## Release and Publish
|
|
299
|
+
|
|
300
|
+
- Changelog: [`CHANGELOG.md`](./CHANGELOG.md)
|
|
301
|
+
- Publishing guide: [`PUBLISHING.md`](./PUBLISHING.md)
|
|
302
|
+
|
|
303
|
+
GitHub Packages (private, `tktchurch` scope) is configured via:
|
|
304
|
+
|
|
305
|
+
- Script: `bun run publish:github`
|
|
306
|
+
- Workflow: [`.github/workflows/publish-github-packages.yml`](./.github/workflows/publish-github-packages.yml)
|
|
307
|
+
- Auth token: `NODE_AUTH_TOKEN` (`GITHUB_TOKEN` in workflow)
|
|
308
|
+
|
|
309
|
+
npmjs (public) publishing ships the **consumer** build only:
|
|
310
|
+
|
|
311
|
+
- Script: `bun run publish:npmjs` (runs `release:check:consumer`)
|
|
312
|
+
- Workflow: [`.github/workflows/publish-npmjs.yml`](./.github/workflows/publish-npmjs.yml)
|
|
313
|
+
- Auth: npm trusted publishing (OIDC, no `NPM_TOKEN` secret) or local `NODE_AUTH_TOKEN`
|
|
314
|
+
|
|
315
|
+
GitHub Packages ships the **full** internal SDK. See [`PUBLISHING.md`](./PUBLISHING.md) for the dual-build matrix.
|