@tideorg/mcp 1.4.0

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 (76) hide show
  1. package/GAP_REGISTER.md +117 -0
  2. package/README.md +38 -0
  3. package/adapters/AGENTS.md +241 -0
  4. package/adapters/CLAUDE.md +146 -0
  5. package/adapters/replit.md +224 -0
  6. package/canon/anti-patterns.md +2133 -0
  7. package/canon/concepts.md +816 -0
  8. package/canon/custom-contracts.md +533 -0
  9. package/canon/delegation.md +195 -0
  10. package/canon/feature-mapping.md +637 -0
  11. package/canon/framework-matrix.md +1125 -0
  12. package/canon/invariants.md +851 -0
  13. package/canon/redirect-handler.md +254 -0
  14. package/canon/tidecloak-bootstrap.md +287 -0
  15. package/canon/tidecloak-endpoints.md +294 -0
  16. package/canon/troubleshooting.md +1494 -0
  17. package/canon/version-policy.md +89 -0
  18. package/mcp-server/dist/index.d.ts +2 -0
  19. package/mcp-server/dist/index.js +605 -0
  20. package/package.json +45 -0
  21. package/playbooks/add-auth-nextjs-existing.md +799 -0
  22. package/playbooks/add-auth-nextjs-fresh.md +654 -0
  23. package/playbooks/add-rbac-nextjs.md +190 -0
  24. package/playbooks/bootstrap-realm-from-template.md +170 -0
  25. package/playbooks/configure-e2ee-roles-and-policies.md +196 -0
  26. package/playbooks/deploy-tidecloak-docker.md +792 -0
  27. package/playbooks/diagnose-broken-login.md +234 -0
  28. package/playbooks/diagnose-missing-roles-or-claims.md +253 -0
  29. package/playbooks/initialize-admin-and-link-account.md +235 -0
  30. package/playbooks/migrate-from-existing-auth.md +198 -0
  31. package/playbooks/protect-api-nextjs.md +451 -0
  32. package/playbooks/protect-routes-nextjs.md +544 -0
  33. package/playbooks/setup-forseti-e2ee.md +756 -0
  34. package/playbooks/setup-iga-admin-panel.md +344 -0
  35. package/playbooks/setup-server-delegation.md +142 -0
  36. package/playbooks/start-tidecloak-dev.md +130 -0
  37. package/playbooks/verify-jwt-server-side.md +439 -0
  38. package/prompts/add-admin-approval-flow.md +50 -0
  39. package/prompts/build-private-customer-portal.md +85 -0
  40. package/prompts/migrate-generic-auth-to-tide.md +67 -0
  41. package/prompts/secure-existing-app.md +80 -0
  42. package/reference-apps/INDEX.md +87 -0
  43. package/reference-apps/encrypted-communication/anti-patterns.md +123 -0
  44. package/reference-apps/encrypted-communication/bootstrap-sequence.md +152 -0
  45. package/reference-apps/encrypted-communication/manifest.yaml +100 -0
  46. package/reference-apps/encrypted-communication/role-policy-matrix.md +80 -0
  47. package/reference-apps/encrypted-communication/scenario.md +134 -0
  48. package/reference-apps/git-pr-signing-service/anti-patterns.md +61 -0
  49. package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +157 -0
  50. package/reference-apps/git-pr-signing-service/manifest.yaml +80 -0
  51. package/reference-apps/git-pr-signing-service/role-policy-matrix.md +99 -0
  52. package/reference-apps/git-pr-signing-service/scenario.md +169 -0
  53. package/reference-apps/iga-admin-governance/anti-patterns.md +133 -0
  54. package/reference-apps/iga-admin-governance/bootstrap-sequence.md +153 -0
  55. package/reference-apps/iga-admin-governance/manifest.yaml +87 -0
  56. package/reference-apps/iga-admin-governance/role-policy-matrix.md +67 -0
  57. package/reference-apps/iga-admin-governance/scenario.md +126 -0
  58. package/reference-apps/organisation-password-manager/anti-patterns.md +71 -0
  59. package/reference-apps/organisation-password-manager/bootstrap-sequence.md +127 -0
  60. package/reference-apps/organisation-password-manager/manifest.yaml +86 -0
  61. package/reference-apps/organisation-password-manager/role-policy-matrix.md +59 -0
  62. package/reference-apps/organisation-password-manager/scenario.md +107 -0
  63. package/reference-apps/policy-governed-signing/anti-patterns.md +67 -0
  64. package/reference-apps/policy-governed-signing/bootstrap-sequence.md +128 -0
  65. package/reference-apps/policy-governed-signing/manifest.yaml +78 -0
  66. package/reference-apps/policy-governed-signing/role-policy-matrix.md +62 -0
  67. package/reference-apps/policy-governed-signing/scenario.md +113 -0
  68. package/skills/tide-diagnostics/SKILL.md +99 -0
  69. package/skills/tide-integration/SKILL.md +136 -0
  70. package/skills/tide-learning-capture/SKILL.md +174 -0
  71. package/skills/tide-rbac-and-e2ee/SKILL.md +214 -0
  72. package/skills/tide-reviewer/SKILL.md +133 -0
  73. package/skills/tide-route-and-api-protection/SKILL.md +201 -0
  74. package/skills/tide-scenario-resolver/SKILL.md +81 -0
  75. package/skills/tide-setup/SKILL.md +218 -0
  76. package/skills/tide-solutions-architect/SKILL.md +130 -0
@@ -0,0 +1,451 @@
1
+ # Protect API Routes in Next.js (Server-Side JWT Verification)
2
+
3
+ Implement real authorization using server-side JWT verification.
4
+
5
+ **CRITICAL**: This is where actual security enforcement happens. Route guards are UI only. APIs must verify JWTs.
6
+
7
+ ---
8
+
9
+ ## When to Use
10
+
11
+ - Protecting API routes that return sensitive data
12
+ - Enforcing authorization before data mutations
13
+ - Verifying user identity server-side
14
+ - Enforcing role-based access control
15
+
16
+ **Always use** for any API that requires authentication.
17
+
18
+ ---
19
+
20
+ ## Prerequisites
21
+
22
+ - Tide authentication installed ([add-auth-nextjs-fresh.md](add-auth-nextjs-fresh.md))
23
+ - Adapter JSON with `jwk` field (present when IGA is enabled on the realm)
24
+ - API routes to protect (App Router: `app/api/*/route.ts`, Pages Router: `pages/api/*.ts`)
25
+
26
+ ---
27
+
28
+ ## Security Model
29
+
30
+ ### Tide JWT Verification
31
+
32
+ 1. Extract JWT from `Authorization` header
33
+ 2. Verify signature using **embedded JWKS from adapter JSON** (not remote endpoint)
34
+ 3. Validate `iss` (issuer), `azp` (authorized party), `exp`, `iat`
35
+ 4. Extract roles from `realm_access.roles` and `resource_access`
36
+ 5. Enforce role requirements
37
+
38
+ **Required**: Tide adapter JSON includes the `jwk` field with embedded JWKS. This enables local JWT verification without network calls. The embedded JWKS is present due to Tide's non-rotating vendor-verifiable key (VVK) model, which locks the system to trust one pre-defined public key, preventing token forgery even if the IAM is compromised.
39
+
40
+ ---
41
+
42
+ ## Files to Create
43
+
44
+ 1. `lib/auth/tidecloakConfig.ts` - Load adapter JSON
45
+ 2. `lib/auth/tideJWT.ts` - JWT verification logic
46
+ 3. `lib/auth/protect.ts` - Reusable auth middleware (optional)
47
+ 4. Update API routes with auth checks
48
+
49
+ See [verify-jwt-server-side.md](verify-jwt-server-side.md) for complete implementation.
50
+
51
+ ---
52
+
53
+ ## Diagnose Existing API Routes
54
+
55
+ If you already have API routes and need to check whether they are correctly protected, run through this checklist:
56
+
57
+ ```bash
58
+ # 1. Does it read from Authorization header (not cookies)?
59
+ grep -r "headers.*authorization\|Authorization" app/api/ --include="*.ts"
60
+ # FAIL if: reads from cookies, session, or has no auth check at all
61
+
62
+ # 2. Does it verify JWT with embedded JWKS (not remote endpoint)?
63
+ grep -r "createLocalJWKSet\|verifyTideJWT\|jwtVerify" lib/auth/ --include="*.ts"
64
+ # FAIL if: uses createRemoteJWKSet (forbidden), dictionary lookup, or no verification
65
+
66
+ # 3. Does it validate issuer and audience?
67
+ grep -r "issuer\|audience" lib/auth/ --include="*.ts"
68
+ # FAIL if: no issuer/audience check in verification options
69
+
70
+ # 4. Does it enforce roles after verification?
71
+ grep -r "hasRole\|withRole\|realm_access" app/api/ lib/auth/ --include="*.ts"
72
+ # FAIL if: returns data without any role check after JWT is verified
73
+ ```
74
+
75
+ Any FAIL means the API route is not properly protected. Use the implementation below to fix it.
76
+
77
+ ---
78
+
79
+ ## Quick Implementation (App Router)
80
+
81
+ This section creates minimal versions of `lib/auth/tidecloakConfig.ts`, `lib/auth/tideJWT.ts`, and `lib/auth/protect.ts`. If you also follow [verify-jwt-server-side.md](verify-jwt-server-side.md), its richer versions **replace** these files (adds DPoP verification, client-role checking, `extractToken`, and `iat` validation).
82
+
83
+ ### Step 0: Install Dependencies
84
+
85
+ ```bash
86
+ npm install jose
87
+ ```
88
+
89
+ ---
90
+
91
+ ### Step 1: Load Adapter JSON
92
+
93
+ ```typescript
94
+ // lib/auth/tidecloakConfig.ts
95
+ import { readFileSync } from 'fs';
96
+ import { join } from 'path';
97
+
98
+ export interface TidecloakConfig {
99
+ realm: string;
100
+ 'auth-server-url': string;
101
+ resource: string;
102
+ jwk: { keys: any[] }; // Tide extension: embedded JWKS for local JWT verification
103
+ vendorId?: string;
104
+ homeOrkUrl?: string;
105
+ }
106
+
107
+ export function loadTideConfig(): TidecloakConfig {
108
+ // Priority: env var > file
109
+ if (process.env.CLIENT_ADAPTER) {
110
+ return JSON.parse(process.env.CLIENT_ADAPTER);
111
+ }
112
+
113
+ const configPath = join(process.cwd(), 'data', 'tidecloak.json');
114
+ return JSON.parse(readFileSync(configPath, 'utf-8'));
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ ### Step 2: Create JWT Verification
121
+
122
+ ```typescript
123
+ // lib/auth/tideJWT.ts
124
+ import { jwtVerify, createLocalJWKSet } from 'jose';
125
+ import type { JWTPayload } from 'jose';
126
+ import { loadTideConfig } from './tidecloakConfig';
127
+
128
+ // Lazy initialization — do NOT load config at module level.
129
+ // Next.js 16 evaluates module-level code during `next build` for static
130
+ // page generation. If tidecloak.json doesn't exist yet (placeholder or
131
+ // pre-bootstrap), eager loading throws at build time.
132
+ let _jwks: ReturnType<typeof createLocalJWKSet> | null = null;
133
+ let _config: ReturnType<typeof loadTideConfig> | null = null;
134
+
135
+ function getConfig() {
136
+ if (!_config) {
137
+ _config = loadTideConfig();
138
+ if (!_config.jwk) {
139
+ throw new Error('Adapter JSON missing jwk field');
140
+ }
141
+ _jwks = createLocalJWKSet(_config.jwk);
142
+ }
143
+ return { config: _config, JWKS: _jwks! };
144
+ }
145
+
146
+ export async function verifyTideJWT(token: string): Promise<JWTPayload> {
147
+ const { config, JWKS } = getConfig();
148
+ const { payload } = await jwtVerify(token, JWKS, {
149
+ issuer: `${config['auth-server-url'].replace(/\/+$/, '')}/realms/${config.realm}`,
150
+ });
151
+
152
+ // TideCloak access tokens use azp (authorized party) for the client ID.
153
+ // The aud claim typically contains "account", not the client ID.
154
+ if (payload.azp !== config.resource) {
155
+ throw new Error('Token azp does not match client');
156
+ }
157
+
158
+ const now = Math.floor(Date.now() / 1000);
159
+ if (payload.exp && payload.exp < now) {
160
+ throw new Error('Token expired');
161
+ }
162
+
163
+ return payload;
164
+ }
165
+
166
+ export function hasRole(payload: JWTPayload, role: string): boolean {
167
+ const realmRoles = (payload.realm_access as any)?.roles || [];
168
+ return realmRoles.includes(role);
169
+ }
170
+ ```
171
+
172
+ **Install dependency**:
173
+ ```bash
174
+ npm install jose
175
+ ```
176
+
177
+ ---
178
+
179
+ ### Step 3: Protect API Route
180
+
181
+ ```typescript
182
+ // app/api/users/route.ts
183
+ import { NextRequest } from 'next/server';
184
+ import { verifyTideJWT, hasRole } from '@/lib/auth/tideJWT';
185
+
186
+ export async function GET(req: NextRequest) {
187
+ const authHeader = req.headers.get('authorization');
188
+ if (!authHeader) {
189
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
190
+ }
191
+
192
+ // Accept both Bearer and DPoP schemes.
193
+ // When useDPoP is enabled, secureFetch upgrades Bearer to DPoP.
194
+ let token: string;
195
+ if (authHeader.startsWith('Bearer ')) {
196
+ token = authHeader.substring(7);
197
+ } else if (authHeader.startsWith('DPoP ')) {
198
+ token = authHeader.substring(5);
199
+ } else {
200
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
201
+ }
202
+
203
+ try {
204
+ const jwt = await verifyTideJWT(token);
205
+
206
+ // Optional: Enforce role
207
+ if (!hasRole(jwt, 'admin')) {
208
+ return Response.json({ error: 'Forbidden' }, { status: 403 });
209
+ }
210
+
211
+ // Proceed with protected operation
212
+ return Response.json({ users: [...] });
213
+ } catch (err) {
214
+ console.error('JWT verification failed:', err);
215
+ return Response.json({ error: 'Invalid token' }, { status: 401 });
216
+ }
217
+ }
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Reusable Middleware Pattern
223
+
224
+ Create middleware for consistent auth:
225
+
226
+ ```typescript
227
+ // lib/auth/protect.ts
228
+ import { NextRequest } from 'next/server';
229
+ import { verifyTideJWT, hasRole } from './tideJWT';
230
+ import type { JWTPayload } from 'jose';
231
+
232
+ type AuthenticatedHandler = (
233
+ req: NextRequest,
234
+ jwt: JWTPayload
235
+ ) => Promise<Response>;
236
+
237
+ export function withAuth(handler: AuthenticatedHandler) {
238
+ return async (req: NextRequest) => {
239
+ const authHeader = req.headers.get('authorization');
240
+ if (!authHeader) {
241
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
242
+ }
243
+
244
+ // Accept both Bearer and DPoP schemes.
245
+ // When useDPoP is enabled, secureFetch upgrades Bearer to DPoP.
246
+ let token: string;
247
+ if (authHeader.startsWith('Bearer ')) {
248
+ token = authHeader.substring(7);
249
+ } else if (authHeader.startsWith('DPoP ')) {
250
+ token = authHeader.substring(5);
251
+ } else {
252
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
253
+ }
254
+
255
+ try {
256
+ const jwt = await verifyTideJWT(token);
257
+ return handler(req, jwt);
258
+ } catch (err) {
259
+ return Response.json({ error: 'Invalid token' }, { status: 401 });
260
+ }
261
+ };
262
+ }
263
+
264
+ export function withRole(role: string, handler: AuthenticatedHandler) {
265
+ return withAuth(async (req, jwt) => {
266
+ if (!hasRole(jwt, role)) {
267
+ return Response.json({ error: 'Forbidden' }, { status: 403 });
268
+ }
269
+ return handler(req, jwt);
270
+ });
271
+ }
272
+ ```
273
+
274
+ **Use in routes**:
275
+
276
+ ```typescript
277
+ // app/api/admin/users/route.ts
278
+ import { withRole } from '@/lib/auth/protect';
279
+
280
+ export const GET = withRole('admin', async (req, jwt) => {
281
+ return Response.json({ users: [...] });
282
+ });
283
+
284
+ export const POST = withRole('admin', async (req, jwt) => {
285
+ const body = await req.json();
286
+ // Create user
287
+ return Response.json({ success: true });
288
+ });
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Pages Router (pages/api)
294
+
295
+ ```typescript
296
+ // pages/api/users.ts
297
+ import type { NextApiRequest, NextApiResponse } from 'next';
298
+ import { verifyTideJWT, hasRole } from '@/lib/auth/tideJWT';
299
+
300
+ export default async function handler(
301
+ req: NextApiRequest,
302
+ res: NextApiResponse
303
+ ) {
304
+ const authHeader = req.headers.authorization;
305
+ if (!authHeader) {
306
+ return res.status(401).json({ error: 'Unauthorized' });
307
+ }
308
+
309
+ // Accept both Bearer and DPoP schemes
310
+ let token: string;
311
+ if (authHeader.startsWith('Bearer ')) {
312
+ token = authHeader.substring(7);
313
+ } else if (authHeader.startsWith('DPoP ')) {
314
+ token = authHeader.substring(5);
315
+ } else {
316
+ return res.status(401).json({ error: 'Unauthorized' });
317
+ }
318
+
319
+ try {
320
+ const jwt = await verifyTideJWT(token);
321
+
322
+ if (!hasRole(jwt, 'admin')) {
323
+ return res.status(403).json({ error: 'Forbidden' });
324
+ }
325
+
326
+ res.json({ users: [...] });
327
+ } catch (err) {
328
+ res.status(401).json({ error: 'Invalid token' });
329
+ }
330
+ }
331
+ ```
332
+
333
+ ---
334
+
335
+ ## DPoP Verification (Required When useDPoP Is Configured)
336
+
337
+ **Required for Tide's full security guarantees**: DPoP (Demonstration of Proof-of-Possession) prevents session hijacking and provides phishing-resistant authentication.
338
+
339
+ When `useDPoP` is configured on the client-side `TideCloakProvider`, the access token contains a `cnf.jkt` claim binding it to the DPoP key. The server **must** verify the DPoP proof for these tokens. A DPoP-bound token sent without a valid proof is a stolen or replayed token — reject it with 401.
340
+
341
+ See [verify-jwt-server-side.md DPoP section](verify-jwt-server-side.md#step-4-dpop-verification-required-for-full-security) for complete implementation.
342
+
343
+ ---
344
+
345
+ ## Verification Checklist
346
+
347
+ ### API Protection
348
+
349
+ - [ ] Unauthenticated request returns 401
350
+ - [ ] Invalid JWT returns 401
351
+ - [ ] Expired JWT returns 401
352
+ - [ ] Valid JWT returns 200
353
+ - [ ] Wrong role returns 403
354
+ - [ ] Correct role returns 200
355
+
356
+ ### Server-Side Tests
357
+
358
+ ```bash
359
+ # Test without token
360
+ curl http://localhost:3000/api/users
361
+ # Should return 401
362
+
363
+ # Test with valid token (get from browser DevTools)
364
+ TOKEN="eyJ..."
365
+ curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/users
366
+ # Should return 200 + data
367
+
368
+ # Test with malformed token
369
+ curl -H "Authorization: Bearer invalid" http://localhost:3000/api/users
370
+ # Should return 401
371
+ ```
372
+
373
+ ---
374
+
375
+ ## Common Failures
376
+
377
+ ### "Adapter JSON missing jwk field"
378
+
379
+ **Cause**: Using generic Keycloak adapter instead of Tide adapter.
380
+
381
+ **Fix**: Re-export adapter via Tide-specific endpoint using `providerId=keycloak-oidc-keycloak-json`. The `jwk` field is present when IGA is enabled on the realm. If absent, verify IGA is enabled and re-export. See [add-auth-nextjs-fresh.md Step 2](add-auth-nextjs-fresh.md#step-2-export-adapter-json-from-tidecloak).
382
+
383
+ ---
384
+
385
+ ### JWT Verification Always Fails
386
+
387
+ **Cause**: Wrong JWKS source or issuer mismatch.
388
+
389
+ **Fix**: See [canon/troubleshooting.md T-02](../canon/troubleshooting.md#t-02-jwt-verification-fails-401-unauthorized).
390
+
391
+ ---
392
+
393
+ ### Role Check Always Fails
394
+
395
+ **Fix**: See [diagnose-missing-roles-or-claims.md](diagnose-missing-roles-or-claims.md).
396
+
397
+ ---
398
+
399
+ ## Do Not Do This
400
+
401
+ ### ❌ Do Not Use Remote JWKS Endpoint
402
+
403
+ ```typescript
404
+ // ❌ WRONG: Fetch JWKS from remote endpoint (forbidden)
405
+ const JWKS = createRemoteJWKSet(
406
+ new URL(`${issuer}/protocol/openid-connect/certs`)
407
+ );
408
+
409
+ // ❌ ALSO WRONG: Remote as fallback
410
+ try { await jwtVerify(token, localJWKS); }
411
+ catch { await jwtVerify(token, createRemoteJWKSet(certsUrl)); }
412
+ ```
413
+
414
+ **Why**: Tide JWTs must be verified against embedded JWKS from adapter JSON only. `createRemoteJWKSet` is forbidden. If `jwk` is missing, fix the adapter export — do not add remote fallback. See [canon/invariants.md I-04](../canon/invariants.md#i-04) and [canon/anti-patterns.md AP-01](../canon/anti-patterns.md#ap-01-treating-tide-as-generic-oidc).
415
+
416
+ ---
417
+
418
+ ### ❌ Do Not Skip JWT Verification
419
+
420
+ ```typescript
421
+ // ❌ WRONG: Trust client-side auth state
422
+ export async function GET(req) {
423
+ // Assumes route guard blocked unauthenticated users
424
+ return Response.json({ sensitiveData: [...] });
425
+ }
426
+ ```
427
+
428
+ **Why**: Route guards can be bypassed. See [canon/invariants.md I-03](../canon/invariants.md#i-03-server-side-jwt-verification-required).
429
+
430
+ ---
431
+
432
+ ## Known Uncertainties
433
+
434
+ None. All critical requirements documented above.
435
+
436
+ ---
437
+
438
+ ## Next Steps
439
+
440
+ 1. [Add RBAC](add-rbac-nextjs.md) - Implement role hierarchies
441
+ 2. [Verify JWT server-side](verify-jwt-server-side.md) - Complete JWT verification guide
442
+ 3. [Diagnose missing roles](diagnose-missing-roles-or-claims.md) - Fix role issues
443
+
444
+ ---
445
+
446
+ ## References
447
+
448
+ - [verify-jwt-server-side.md](verify-jwt-server-side.md) - Complete implementation
449
+ - [canon/invariants.md I-03, I-04](../canon/invariants.md) - JWT verification rules
450
+ - [canon/anti-patterns.md AP-01, AP-02](../canon/anti-patterns.md) - Auth mistakes
451
+ - keylessh: `server/lib/auth/tideJWT.ts` (DC-01)