@cleverclouds/auth-client 1.0.0 → 1.0.2

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/AGENTS.md CHANGED
@@ -1,379 +1,379 @@
1
- # @cleverclouds/auth-client — AI Agent & LLM Implementation Guide
2
-
3
- > **Note for AI Coding Assistants (Claude, Cursor, Copilot, Antigravity, Devin, Codex):**
4
- > Use this document as the authoritative specification when implementing or refactoring authentication and authorization in downstream applications using `@cleverclouds/auth-client`.
5
-
6
- ---
7
-
8
- ## 1. Core Mental Model & Architectural Invariants
9
-
10
- CleverClouds Identity is a centralized SSO and Identity Management service. Downstream applications (e.g. `projects`, `hr`, `finance`, `ops`) delegate authentication and role/permission assignment to Identity.
11
-
12
- ### Identity Boundary Rules
13
- 1. **Zero Direct DB Access**: Downstream applications must **never** connect directly to the Identity MongoDB cluster. All communication flows over HTTPS backchannel calls or cryptographic cookies.
14
- 2. **Identity vs. Domain Authorization**:
15
- - Identity tells downstream apps: *Who is the user?* (`userId`, `email`, `name`, `status`), *What roles do they hold in THIS app?* (`roles: ["PROJECT_MANAGER"]`), and *What permissions do they have?* (`permissions: ["projects.*"]`).
16
- - The downstream application remains **100% responsible** for resource-level authorization (e.g., whether user A owns project #42).
17
- 3. **Mandatory HMAC Cookie Signing**: Downstream session cookies MUST be cryptographically signed using HMAC-SHA256 via a configured `sessionSecret`. The client strictly prohibits unsigned or unverified cookie consumption.
18
- 4. **Single-Use Tickets**: SSO tickets (`cc_ticket_...`) are valid for **60 seconds** and are burned upon first exchange. Never reuse or cache tickets.
19
- 5. **Open Redirect Defense**: When redirecting users post-callback, downstream handlers MUST validate `returnTo` URLs to ensure they are relative paths (starting with `/`, not `//` or `/\`) or match a strict whitelist.
20
-
21
- ---
22
-
23
- ## 2. Package Entrypoints & Subpath Imports
24
-
25
- ```typescript
26
- // 1. Core client, types, error classes, and standalone RBAC evaluators
27
- import {
28
- CleverCloudsAuthClient,
29
- hasPermission,
30
- hasAnyPermission,
31
- hasAllPermissions,
32
- hasRole,
33
- hasAnyRole,
34
- CleverCloudsAuthError,
35
- AppAccessDeniedError,
36
- InvalidTicketError,
37
- InvalidCredentialsError,
38
- } from '@cleverclouds/auth-client';
39
-
40
- // 2. Express.js middleware and route helpers
41
- import { createAuthMiddleware } from '@cleverclouds/auth-client/express';
42
-
43
- // 3. Next.js App Router helpers (Server Components, Route Handlers, Server Actions)
44
- import { getServerSession, protectRoute } from '@cleverclouds/auth-client/nextjs';
45
-
46
- // 4. Low-level HMAC-SHA256 cookie signer & verifier
47
- import { signCookie, verifyCookie } from '@cleverclouds/auth-client/cookies';
48
- ```
49
-
50
- ---
51
-
52
- ## 3. Required Environment Variables
53
-
54
- Downstream applications must define these environment variables:
55
-
56
- ```env
57
- # URL where CleverClouds Identity is hosted
58
- CC_IDENTITY_URL=https://auth.cleverclouds.in
59
-
60
- # Application identifier registered in CleverClouds Identity
61
- CC_APP_SLUG=projects
62
-
63
- # Application API key generated in CleverClouds Identity Admin Console
64
- CC_APP_KEY=cc_app_projects_xxxxxxxxxxxxxxxx
65
-
66
- # Callback URL registered in application's redirectUrls
67
- CC_REDIRECT_URI=https://projects.cleverclouds.in/api/auth/callback
68
-
69
- # Strong secret for signing downstream session cookies (min 32 characters)
70
- SESSION_SECRET=super_strong_random_secret_at_least_32_chars_long
71
- ```
72
-
73
- ---
74
-
75
- ## 4. Initializing the Client Singleton
76
-
77
- Create a shared singleton instance (e.g. `src/lib/auth.ts` or `src/auth.ts`):
78
-
79
- ```typescript
80
- import { CleverCloudsAuthClient } from '@cleverclouds/auth-client';
81
-
82
- export const auth = new CleverCloudsAuthClient({
83
- identityBaseUrl: process.env.CC_IDENTITY_URL || 'http://localhost:3000',
84
- applicationSlug: process.env.CC_APP_SLUG || 'projects',
85
- applicationApiKey: process.env.CC_APP_KEY!,
86
- redirectUri: process.env.CC_REDIRECT_URI || 'http://localhost:3001/api/auth/callback',
87
- sessionSecret: process.env.SESSION_SECRET!, // Mandatory for session cookie signing
88
- });
89
- ```
90
-
91
- ---
92
-
93
- ## 5. Implementation Recipes
94
-
95
- ### Pattern A: Express.js (API & SSR)
96
-
97
- ```typescript
98
- import express from 'express';
99
- import cookieParser from 'cookie-parser';
100
- import { createAuthMiddleware } from '@cleverclouds/auth-client/express';
101
- import { auth } from './lib/auth.js';
102
-
103
- const app = express();
104
- app.use(express.json());
105
- app.use(cookieParser()); // REQUIRED: cookie-parser middleware must precede auth middleware
106
-
107
- // Initialize middleware adapter
108
- const {
109
- requireAuth,
110
- requirePermission,
111
- requireRole,
112
- handleCallback,
113
- handleLogout,
114
- } = createAuthMiddleware(auth, {
115
- cookieName: 'projects_session',
116
- cookieOptions: {
117
- httpOnly: true,
118
- secure: process.env.NODE_ENV === 'production',
119
- sameSite: 'lax',
120
- path: '/',
121
- maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
122
- },
123
- });
124
-
125
- // 1. SSO Callback Route — Exchanges ticket and sets signed session cookie
126
- app.get('/api/auth/callback', handleCallback({
127
- defaultReturnTo: '/dashboard',
128
- allowedRedirectHosts: ['projects.cleverclouds.in', 'localhost:3001'],
129
- }));
130
-
131
- // 2. Logout Route — Clears session cookie and redirects
132
- app.get('/logout', handleLogout({
133
- redirectTo: '/login',
134
- }));
135
-
136
- // 3. Authenticated Route — Attaches req.auth: VerificationResult
137
- app.get('/api/me', requireAuth(), (req, res) => {
138
- res.json({
139
- user: req.auth.user,
140
- roles: req.auth.roles,
141
- permissions: req.auth.permissions,
142
- });
143
- });
144
-
145
- // 4. Permission-Gated Route (supports wildcards e.g. 'projects.*')
146
- app.post('/api/projects', requirePermission('projects.create'), async (req, res) => {
147
- // Safe: user is guaranteed to have 'projects.create' or 'projects.*' or '*.*'
148
- res.json({ message: 'Project created' });
149
- });
150
-
151
- // 5. Role-Gated Route (case-insensitive role check)
152
- app.delete('/api/projects/:id', requireRole('project_admin'), async (req, res) => {
153
- res.json({ message: 'Project deleted' });
154
- });
155
-
156
- // Optional: Custom Login Trigger (redirects browser to Identity)
157
- app.get('/login', (req, res) => {
158
- const returnTo = typeof req.query.returnTo === 'string' ? req.query.returnTo : '/dashboard';
159
- const loginUrl = auth.getAuthorizationUrl({ returnTo });
160
- res.redirect(loginUrl);
161
- });
162
- ```
163
-
164
- ---
165
-
166
- ### Pattern B: Next.js App Router (14/15)
167
-
168
- #### 1. Server Components & Route Handlers (`getServerSession`)
169
-
170
- ```typescript
171
- // app/dashboard/page.tsx (Server Component)
172
- import { redirect } from 'next/navigation';
173
- import { getServerSession } from '@cleverclouds/auth-client/nextjs';
174
- import { auth } from '@/lib/auth';
175
-
176
- export default async function DashboardPage() {
177
- const session = await getServerSession(auth, { cookieName: 'projects_session' });
178
-
179
- if (!session) {
180
- redirect(auth.getAuthorizationUrl({ returnTo: '/dashboard' }));
181
- }
182
-
183
- return (
184
- <main>
185
- <h1>Welcome, {session.user.name}</h1>
186
- <p>Email: {session.user.email}</p>
187
- <p>Roles: {session.roles.join(', ')}</p>
188
- </main>
189
- );
190
- }
191
- ```
192
-
193
- #### 2. Route Guard Helper (`protectRoute`)
194
-
195
- ```typescript
196
- // app/api/projects/route.ts (Route Handler)
197
- import { NextResponse } from 'next/server';
198
- import { protectRoute } from '@cleverclouds/auth-client/nextjs';
199
- import { auth } from '@/lib/auth';
200
-
201
- export async function POST(req: Request) {
202
- const { authorized, session, response } = await protectRoute(auth, {
203
- request: req,
204
- permission: 'projects.create',
205
- cookieName: 'projects_session',
206
- });
207
-
208
- if (!authorized) {
209
- return response!; // Returns 401 Unauthorized or 403 Forbidden with JSON error
210
- }
211
-
212
- const body = await req.json();
213
- return NextResponse.json({
214
- success: true,
215
- createdBy: session!.user.email,
216
- });
217
- }
218
- ```
219
-
220
- #### 3. SSO Callback Route Handler
221
-
222
- ```typescript
223
- // app/api/auth/callback/route.ts
224
- import { NextRequest, NextResponse } from 'next/server';
225
- import { auth } from '@/lib/auth';
226
-
227
- export async function GET(req: NextRequest) {
228
- const ticket = req.nextUrl.searchParams.get('ticket');
229
- const returnTo = req.nextUrl.searchParams.get('returnTo') || '/dashboard';
230
-
231
- if (!ticket) {
232
- return NextResponse.json({ error: 'Missing ticket' }, { status: 400 });
233
- }
234
-
235
- const exchange = await auth.exchangeTicket(ticket);
236
- if (!exchange.success) {
237
- return NextResponse.json({ error: exchange.error.message }, { status: 401 });
238
- }
239
-
240
- // Cryptographically sign session payload
241
- const signedCookie = auth.signSessionCookie(exchange.data, {
242
- expiresInSeconds: 7 * 24 * 60 * 60,
243
- });
244
-
245
- // Enforce relative redirect (anti-open-redirect)
246
- const safePath = returnTo.startsWith('/') && !returnTo.startsWith('//') ? returnTo : '/dashboard';
247
- const response = NextResponse.redirect(new URL(safePath, req.url));
248
-
249
- response.cookies.set('projects_session', signedCookie, {
250
- httpOnly: true,
251
- secure: process.env.NODE_ENV === 'production',
252
- sameSite: 'lax',
253
- path: '/',
254
- maxAge: 7 * 24 * 60 * 60,
255
- });
256
-
257
- return response;
258
- }
259
- ```
260
-
261
- ---
262
-
263
- ### Pattern C: Fastify / Raw Node HTTP
264
-
265
- ```typescript
266
- import Fastify from 'fastify';
267
- import fastifyCookie from '@fastify/cookie';
268
- import { auth } from './lib/auth.js';
269
-
270
- const fastify = Fastify();
271
- fastify.register(fastifyCookie);
272
-
273
- // Authentication Hook
274
- fastify.addHook('preHandler', async (req, reply) => {
275
- const cookieValue = req.cookies.projects_session;
276
- if (!cookieValue) return;
277
-
278
- const session = auth.verifySessionCookie(cookieValue);
279
- if (session) {
280
- (req as any).auth = session;
281
- }
282
- });
283
-
284
- // Guard helper
285
- function requirePermission(permission: string) {
286
- return async (req: any, reply: any) => {
287
- if (!req.auth) {
288
- return reply.status(401).send({ error: 'Unauthorized' });
289
- }
290
- if (!auth.hasPermission(req.auth.permissions, permission)) {
291
- return reply.status(403).send({ error: 'Forbidden', required: permission });
292
- }
293
- };
294
- }
295
- ```
296
-
297
- ---
298
-
299
- ## 6. Role & Permission Evaluator Rules
300
-
301
- The library provides zero-dependency pure evaluation functions:
302
-
303
- ```typescript
304
- import {
305
- hasPermission,
306
- hasAnyPermission,
307
- hasAllPermissions,
308
- hasRole,
309
- hasAnyRole,
310
- } from '@cleverclouds/auth-client';
311
-
312
- // 1. Exact match
313
- hasPermission(['projects.read', 'projects.create'], 'projects.create'); // true
314
-
315
- // 2. Resource Wildcard matching (matches any action under the resource namespace)
316
- hasPermission(['projects.*'], 'projects.delete'); // true
317
- hasPermission(['projects.*'], 'finance.read'); // false
318
-
319
- // 3. Platform Wildcard matching
320
- hasPermission(['*.*'], 'anything.action'); // true
321
- hasPermission(['*'], 'anything.action'); // true
322
-
323
- // 4. Multi-permission helpers
324
- hasAnyPermission(['projects.read'], ['projects.write', 'projects.read']); // true
325
- hasAllPermissions(['projects.read'], ['projects.write', 'projects.read']); // false
326
-
327
- // 5. Role matching (case-insensitive)
328
- hasRole(['PROJECT_MANAGER'], 'project_manager'); // true
329
- hasAnyRole(['DEVELOPER'], ['ADMIN', 'DEVELOPER']); // true
330
- ```
331
-
332
- ---
333
-
334
- ## 7. TypeScript Type Definitions
335
-
336
- ```typescript
337
- export interface AuthUser {
338
- id: string;
339
- email: string;
340
- name: string;
341
- status: 'ACTIVE' | 'INACTIVE' | 'SUSPENDED';
342
- platformRole?: string;
343
- avatarUrl?: string;
344
- }
345
-
346
- export interface ApplicationMetadata {
347
- id: string;
348
- slug: string;
349
- name: string;
350
- }
351
-
352
- export interface VerificationResult {
353
- valid: boolean;
354
- user: AuthUser;
355
- application: ApplicationMetadata;
356
- roles: string[];
357
- permissions: string[];
358
- }
359
-
360
- export interface ClientConfig {
361
- identityBaseUrl?: string; // Default: 'http://localhost:3000'
362
- applicationSlug: string; // e.g. 'projects'
363
- applicationApiKey: string; // 'cc_app_...'
364
- redirectUri: string; // e.g. 'http://localhost:3001/api/auth/callback'
365
- sessionSecret?: string; // Mandatory for cookie signing/verifying
366
- }
367
- ```
368
-
369
- ---
370
-
371
- ## 8. Critical Checklist for AI Agents
372
-
373
- When writing or refactoring code that uses `@cleverclouds/auth-client`, verify that:
374
- - [ ] `cookie-parser` is registered before `createAuthMiddleware` in Express apps.
375
- - [ ] `SESSION_SECRET` is passed in `ClientConfig` whenever cookies are used.
376
- - [ ] All redirect destinations in callbacks are checked for open redirects (`safePath.startsWith('/') && !safePath.startsWith('//')`).
377
- - [ ] `ticket` is validated and rejected with HTTP 400 if missing from callback queries.
378
- - [ ] Cookies are configured with `httpOnly: true`, `secure: true` (in production), and `sameSite: 'lax'`.
379
- - [ ] Domain authorization checks (e.g. database entity ownership) remain enforced downstream; do not assume identity permissions imply row-level database ownership.
1
+ # @cleverclouds/auth-client — AI Agent & LLM Implementation Guide
2
+
3
+ > **Note for AI Coding Assistants (Claude, Cursor, Copilot, Antigravity, Devin, Codex):**
4
+ > Use this document as the authoritative specification when implementing or refactoring authentication and authorization in downstream applications using `@cleverclouds/auth-client`.
5
+
6
+ ---
7
+
8
+ ## 1. Core Mental Model & Architectural Invariants
9
+
10
+ CleverClouds Identity is a centralized SSO and Identity Management service. Downstream applications (e.g. `projects`, `hr`, `finance`, `ops`) delegate authentication and role/permission assignment to Identity.
11
+
12
+ ### Identity Boundary Rules
13
+ 1. **Zero Direct DB Access**: Downstream applications must **never** connect directly to the Identity MongoDB cluster. All communication flows over HTTPS backchannel calls or cryptographic cookies.
14
+ 2. **Identity vs. Domain Authorization**:
15
+ - Identity tells downstream apps: *Who is the user?* (`userId`, `email`, `name`, `status`), *What roles do they hold in THIS app?* (`roles: ["PROJECT_MANAGER"]`), and *What permissions do they have?* (`permissions: ["projects.*"]`).
16
+ - The downstream application remains **100% responsible** for resource-level authorization (e.g., whether user A owns project #42).
17
+ 3. **Mandatory HMAC Cookie Signing**: Downstream session cookies MUST be cryptographically signed using HMAC-SHA256 via a configured `sessionSecret`. The client strictly prohibits unsigned or unverified cookie consumption.
18
+ 4. **Single-Use Tickets**: SSO tickets (`cc_ticket_...`) are valid for **60 seconds** and are burned upon first exchange. Never reuse or cache tickets.
19
+ 5. **Open Redirect Defense**: When redirecting users post-callback, downstream handlers MUST validate `returnTo` URLs to ensure they are relative paths (starting with `/`, not `//` or `/\`) or match a strict whitelist.
20
+
21
+ ---
22
+
23
+ ## 2. Package Entrypoints & Subpath Imports
24
+
25
+ ```typescript
26
+ // 1. Core client, types, error classes, and standalone RBAC evaluators
27
+ import {
28
+ CleverCloudsAuthClient,
29
+ hasPermission,
30
+ hasAnyPermission,
31
+ hasAllPermissions,
32
+ hasRole,
33
+ hasAnyRole,
34
+ CleverCloudsAuthError,
35
+ AppAccessDeniedError,
36
+ InvalidTicketError,
37
+ InvalidCredentialsError,
38
+ } from '@cleverclouds/auth-client';
39
+
40
+ // 2. Express.js middleware and route helpers
41
+ import { createAuthMiddleware } from '@cleverclouds/auth-client/express';
42
+
43
+ // 3. Next.js App Router helpers (Server Components, Route Handlers, Server Actions)
44
+ import { getServerSession, protectRoute } from '@cleverclouds/auth-client/nextjs';
45
+
46
+ // 4. Low-level HMAC-SHA256 cookie signer & verifier
47
+ import { signCookie, verifyCookie } from '@cleverclouds/auth-client/cookies';
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 3. Required Environment Variables
53
+
54
+ Downstream applications must define these environment variables:
55
+
56
+ ```env
57
+ # URL where CleverClouds Identity is hosted
58
+ CC_IDENTITY_URL=https://auth.cleverclouds.in
59
+
60
+ # Application identifier registered in CleverClouds Identity
61
+ CC_APP_SLUG=projects
62
+
63
+ # Application API key generated in CleverClouds Identity Admin Console
64
+ CC_APP_KEY=cc_app_projects_xxxxxxxxxxxxxxxx
65
+
66
+ # Callback URL registered in application's redirectUrls
67
+ CC_REDIRECT_URI=https://projects.cleverclouds.in/api/auth/callback
68
+
69
+ # Strong secret for signing downstream session cookies (min 32 characters)
70
+ SESSION_SECRET=super_strong_random_secret_at_least_32_chars_long
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 4. Initializing the Client Singleton
76
+
77
+ Create a shared singleton instance (e.g. `src/lib/auth.ts` or `src/auth.ts`):
78
+
79
+ ```typescript
80
+ import { CleverCloudsAuthClient } from '@cleverclouds/auth-client';
81
+
82
+ export const auth = new CleverCloudsAuthClient({
83
+ identityBaseUrl: process.env.CC_IDENTITY_URL || 'http://localhost:3000',
84
+ applicationSlug: process.env.CC_APP_SLUG || 'projects',
85
+ applicationApiKey: process.env.CC_APP_KEY!,
86
+ redirectUri: process.env.CC_REDIRECT_URI || 'http://localhost:3001/api/auth/callback',
87
+ sessionSecret: process.env.SESSION_SECRET!, // Mandatory for session cookie signing
88
+ });
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 5. Implementation Recipes
94
+
95
+ ### Pattern A: Express.js (API & SSR)
96
+
97
+ ```typescript
98
+ import express from 'express';
99
+ import cookieParser from 'cookie-parser';
100
+ import { createAuthMiddleware } from '@cleverclouds/auth-client/express';
101
+ import { auth } from './lib/auth.js';
102
+
103
+ const app = express();
104
+ app.use(express.json());
105
+ app.use(cookieParser()); // REQUIRED: cookie-parser middleware must precede auth middleware
106
+
107
+ // Initialize middleware adapter
108
+ const {
109
+ requireAuth,
110
+ requirePermission,
111
+ requireRole,
112
+ handleCallback,
113
+ handleLogout,
114
+ } = createAuthMiddleware(auth, {
115
+ cookieName: 'projects_session',
116
+ cookieOptions: {
117
+ httpOnly: true,
118
+ secure: process.env.NODE_ENV === 'production',
119
+ sameSite: 'lax',
120
+ path: '/',
121
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
122
+ },
123
+ });
124
+
125
+ // 1. SSO Callback Route — Exchanges ticket and sets signed session cookie
126
+ app.get('/api/auth/callback', handleCallback({
127
+ defaultReturnTo: '/dashboard',
128
+ allowedRedirectHosts: ['projects.cleverclouds.in', 'localhost:3001'],
129
+ }));
130
+
131
+ // 2. Logout Route — Clears session cookie and redirects
132
+ app.get('/logout', handleLogout({
133
+ redirectTo: '/login',
134
+ }));
135
+
136
+ // 3. Authenticated Route — Attaches req.auth: VerificationResult
137
+ app.get('/api/me', requireAuth(), (req, res) => {
138
+ res.json({
139
+ user: req.auth.user,
140
+ roles: req.auth.roles,
141
+ permissions: req.auth.permissions,
142
+ });
143
+ });
144
+
145
+ // 4. Permission-Gated Route (supports wildcards e.g. 'projects.*')
146
+ app.post('/api/projects', requirePermission('projects.create'), async (req, res) => {
147
+ // Safe: user is guaranteed to have 'projects.create' or 'projects.*' or '*.*'
148
+ res.json({ message: 'Project created' });
149
+ });
150
+
151
+ // 5. Role-Gated Route (case-insensitive role check)
152
+ app.delete('/api/projects/:id', requireRole('project_admin'), async (req, res) => {
153
+ res.json({ message: 'Project deleted' });
154
+ });
155
+
156
+ // Optional: Custom Login Trigger (redirects browser to Identity)
157
+ app.get('/login', (req, res) => {
158
+ const returnTo = typeof req.query.returnTo === 'string' ? req.query.returnTo : '/dashboard';
159
+ const loginUrl = auth.getAuthorizationUrl({ returnTo });
160
+ res.redirect(loginUrl);
161
+ });
162
+ ```
163
+
164
+ ---
165
+
166
+ ### Pattern B: Next.js App Router (14/15)
167
+
168
+ #### 1. Server Components & Route Handlers (`getServerSession`)
169
+
170
+ ```typescript
171
+ // app/dashboard/page.tsx (Server Component)
172
+ import { redirect } from 'next/navigation';
173
+ import { getServerSession } from '@cleverclouds/auth-client/nextjs';
174
+ import { auth } from '@/lib/auth';
175
+
176
+ export default async function DashboardPage() {
177
+ const session = await getServerSession(auth, { cookieName: 'projects_session' });
178
+
179
+ if (!session) {
180
+ redirect(auth.getAuthorizationUrl({ returnTo: '/dashboard' }));
181
+ }
182
+
183
+ return (
184
+ <main>
185
+ <h1>Welcome, {session.user.name}</h1>
186
+ <p>Email: {session.user.email}</p>
187
+ <p>Roles: {session.roles.join(', ')}</p>
188
+ </main>
189
+ );
190
+ }
191
+ ```
192
+
193
+ #### 2. Route Guard Helper (`protectRoute`)
194
+
195
+ ```typescript
196
+ // app/api/projects/route.ts (Route Handler)
197
+ import { NextResponse } from 'next/server';
198
+ import { protectRoute } from '@cleverclouds/auth-client/nextjs';
199
+ import { auth } from '@/lib/auth';
200
+
201
+ export async function POST(req: Request) {
202
+ const { authorized, session, response } = await protectRoute(auth, {
203
+ request: req,
204
+ permission: 'projects.create',
205
+ cookieName: 'projects_session',
206
+ });
207
+
208
+ if (!authorized) {
209
+ return response!; // Returns 401 Unauthorized or 403 Forbidden with JSON error
210
+ }
211
+
212
+ const body = await req.json();
213
+ return NextResponse.json({
214
+ success: true,
215
+ createdBy: session!.user.email,
216
+ });
217
+ }
218
+ ```
219
+
220
+ #### 3. SSO Callback Route Handler
221
+
222
+ ```typescript
223
+ // app/api/auth/callback/route.ts
224
+ import { NextRequest, NextResponse } from 'next/server';
225
+ import { auth } from '@/lib/auth';
226
+
227
+ export async function GET(req: NextRequest) {
228
+ const ticket = req.nextUrl.searchParams.get('ticket');
229
+ const returnTo = req.nextUrl.searchParams.get('returnTo') || '/dashboard';
230
+
231
+ if (!ticket) {
232
+ return NextResponse.json({ error: 'Missing ticket' }, { status: 400 });
233
+ }
234
+
235
+ const exchange = await auth.exchangeTicket(ticket);
236
+ if (!exchange.success) {
237
+ return NextResponse.json({ error: exchange.error.message }, { status: 401 });
238
+ }
239
+
240
+ // Cryptographically sign session payload
241
+ const signedCookie = auth.signSessionCookie(exchange.data, {
242
+ expiresInSeconds: 7 * 24 * 60 * 60,
243
+ });
244
+
245
+ // Enforce relative redirect (anti-open-redirect)
246
+ const safePath = returnTo.startsWith('/') && !returnTo.startsWith('//') ? returnTo : '/dashboard';
247
+ const response = NextResponse.redirect(new URL(safePath, req.url));
248
+
249
+ response.cookies.set('projects_session', signedCookie, {
250
+ httpOnly: true,
251
+ secure: process.env.NODE_ENV === 'production',
252
+ sameSite: 'lax',
253
+ path: '/',
254
+ maxAge: 7 * 24 * 60 * 60,
255
+ });
256
+
257
+ return response;
258
+ }
259
+ ```
260
+
261
+ ---
262
+
263
+ ### Pattern C: Fastify / Raw Node HTTP
264
+
265
+ ```typescript
266
+ import Fastify from 'fastify';
267
+ import fastifyCookie from '@fastify/cookie';
268
+ import { auth } from './lib/auth.js';
269
+
270
+ const fastify = Fastify();
271
+ fastify.register(fastifyCookie);
272
+
273
+ // Authentication Hook
274
+ fastify.addHook('preHandler', async (req, reply) => {
275
+ const cookieValue = req.cookies.projects_session;
276
+ if (!cookieValue) return;
277
+
278
+ const session = auth.verifySessionCookie(cookieValue);
279
+ if (session) {
280
+ (req as any).auth = session;
281
+ }
282
+ });
283
+
284
+ // Guard helper
285
+ function requirePermission(permission: string) {
286
+ return async (req: any, reply: any) => {
287
+ if (!req.auth) {
288
+ return reply.status(401).send({ error: 'Unauthorized' });
289
+ }
290
+ if (!auth.hasPermission(req.auth.permissions, permission)) {
291
+ return reply.status(403).send({ error: 'Forbidden', required: permission });
292
+ }
293
+ };
294
+ }
295
+ ```
296
+
297
+ ---
298
+
299
+ ## 6. Role & Permission Evaluator Rules
300
+
301
+ The library provides zero-dependency pure evaluation functions:
302
+
303
+ ```typescript
304
+ import {
305
+ hasPermission,
306
+ hasAnyPermission,
307
+ hasAllPermissions,
308
+ hasRole,
309
+ hasAnyRole,
310
+ } from '@cleverclouds/auth-client';
311
+
312
+ // 1. Exact match
313
+ hasPermission(['projects.read', 'projects.create'], 'projects.create'); // true
314
+
315
+ // 2. Resource Wildcard matching (matches any action under the resource namespace)
316
+ hasPermission(['projects.*'], 'projects.delete'); // true
317
+ hasPermission(['projects.*'], 'finance.read'); // false
318
+
319
+ // 3. Platform Wildcard matching
320
+ hasPermission(['*.*'], 'anything.action'); // true
321
+ hasPermission(['*'], 'anything.action'); // true
322
+
323
+ // 4. Multi-permission helpers
324
+ hasAnyPermission(['projects.read'], ['projects.write', 'projects.read']); // true
325
+ hasAllPermissions(['projects.read'], ['projects.write', 'projects.read']); // false
326
+
327
+ // 5. Role matching (case-insensitive)
328
+ hasRole(['PROJECT_MANAGER'], 'project_manager'); // true
329
+ hasAnyRole(['DEVELOPER'], ['ADMIN', 'DEVELOPER']); // true
330
+ ```
331
+
332
+ ---
333
+
334
+ ## 7. TypeScript Type Definitions
335
+
336
+ ```typescript
337
+ export interface AuthUser {
338
+ id: string;
339
+ email: string;
340
+ name: string;
341
+ status: 'ACTIVE' | 'INACTIVE' | 'SUSPENDED';
342
+ platformRole?: string;
343
+ avatarUrl?: string;
344
+ }
345
+
346
+ export interface ApplicationMetadata {
347
+ id: string;
348
+ slug: string;
349
+ name: string;
350
+ }
351
+
352
+ export interface VerificationResult {
353
+ valid: boolean;
354
+ user: AuthUser;
355
+ application: ApplicationMetadata;
356
+ roles: string[];
357
+ permissions: string[];
358
+ }
359
+
360
+ export interface ClientConfig {
361
+ identityBaseUrl?: string; // Default: 'http://localhost:3000'
362
+ applicationSlug: string; // e.g. 'projects'
363
+ applicationApiKey: string; // 'cc_app_...'
364
+ redirectUri: string; // e.g. 'http://localhost:3001/api/auth/callback'
365
+ sessionSecret?: string; // Mandatory for cookie signing/verifying
366
+ }
367
+ ```
368
+
369
+ ---
370
+
371
+ ## 8. Critical Checklist for AI Agents
372
+
373
+ When writing or refactoring code that uses `@cleverclouds/auth-client`, verify that:
374
+ - [ ] `cookie-parser` is registered before `createAuthMiddleware` in Express apps.
375
+ - [ ] `SESSION_SECRET` is passed in `ClientConfig` whenever cookies are used.
376
+ - [ ] All redirect destinations in callbacks are checked for open redirects (`safePath.startsWith('/') && !safePath.startsWith('//')`).
377
+ - [ ] `ticket` is validated and rejected with HTTP 400 if missing from callback queries.
378
+ - [ ] Cookies are configured with `httpOnly: true`, `secure: true` (in production), and `sameSite: 'lax'`.
379
+ - [ ] Domain authorization checks (e.g. database entity ownership) remain enforced downstream; do not assume identity permissions imply row-level database ownership.