@bluealba/platform-cli 1.0.1 → 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.
Files changed (42) hide show
  1. package/dist/index.js +277 -9
  2. package/docs/404.mdx +5 -0
  3. package/docs/architecture/api-explorer.mdx +478 -0
  4. package/docs/architecture/architecture-diagrams.mdx +12 -0
  5. package/docs/architecture/authentication-system.mdx +903 -0
  6. package/docs/architecture/authorization-system.mdx +886 -0
  7. package/docs/architecture/bootstrap.mdx +1442 -0
  8. package/docs/architecture/gateway-architecture.mdx +845 -0
  9. package/docs/architecture/multi-tenancy.mdx +1150 -0
  10. package/docs/architecture/overview.mdx +776 -0
  11. package/docs/architecture/scheduler.mdx +818 -0
  12. package/docs/architecture/shell.mdx +885 -0
  13. package/docs/architecture/ui-extension-points.mdx +781 -0
  14. package/docs/architecture/user-states.mdx +794 -0
  15. package/docs/development/overview.mdx +21 -0
  16. package/docs/development/workflow.mdx +914 -0
  17. package/docs/getting-started/core-concepts.mdx +892 -0
  18. package/docs/getting-started/installation.mdx +780 -0
  19. package/docs/getting-started/overview.mdx +83 -0
  20. package/docs/getting-started/quick-start.mdx +940 -0
  21. package/docs/guides/adding-documentation-sites.mdx +1367 -0
  22. package/docs/guides/creating-services.mdx +1736 -0
  23. package/docs/guides/creating-ui-modules.mdx +1860 -0
  24. package/docs/guides/identity-providers.mdx +1007 -0
  25. package/docs/guides/mermaid-diagrams.mdx +212 -0
  26. package/docs/guides/using-feature-flags.mdx +1059 -0
  27. package/docs/guides/working-with-rooms.mdx +566 -0
  28. package/docs/index.mdx +57 -0
  29. package/docs/platform-cli/commands.mdx +604 -0
  30. package/docs/platform-cli/overview.mdx +195 -0
  31. package/package.json +5 -2
  32. package/skills/ba-platform/platform-cli.skill.md +26 -0
  33. package/skills/ba-platform/platform.skill.md +35 -0
  34. package/templates/application-monorepo-template/gitignore +95 -0
  35. package/templates/bootstrap-service-template/gitignore +57 -0
  36. package/templates/bootstrap-service-template/src/main.ts +6 -16
  37. package/templates/customization-ui-module-template/gitignore +73 -0
  38. package/templates/nestjs-service-module-template/gitignore +56 -0
  39. package/templates/platform-init-template/{{platformName}}-core/gitignore +97 -0
  40. package/templates/react-ui-module-template/Dockerfile +1 -1
  41. package/templates/react-ui-module-template/caddy/Caddyfile +1 -1
  42. package/templates/react-ui-module-template/gitignore +72 -0
@@ -0,0 +1,903 @@
1
+ ---
2
+ title: Authentication System
3
+ description: Deep dive into the Blue Alba Platform authentication architecture - JWT, multi-IDP, OAuth 2.0, API keys, service auth, and impersonation
4
+ ---
5
+
6
+ import { Card, CardGrid, Aside, Tabs, TabItem } from '@astrojs/starlight/components';
7
+
8
+ The Blue Alba Platform implements a comprehensive authentication system that supports multiple identity providers, authentication methods, and advanced features like user impersonation.
9
+
10
+ ## Authentication Architecture Overview
11
+
12
+ The platform supports multiple authentication mechanisms:
13
+
14
+ <CardGrid stagger>
15
+ <Card title="JWT-Based Sessions" icon="approve-check">
16
+ Primary authentication using JWT tokens stored in HTTP-only cookies for browser-based access
17
+ </Card>
18
+
19
+ <Card title="Multi-IDP Support" icon="star">
20
+ Integration with multiple identity providers: Okta, EntraId, OneLogin, Github, AWS Cognito
21
+ </Card>
22
+
23
+ <Card title="API Key Authentication" icon="seti:lock">
24
+ Long-lived API keys for programmatic access and integrations
25
+ </Card>
26
+
27
+ <Card title="Service-to-Service Auth" icon="seti:config">
28
+ Secure service authentication using shared secrets for internal communication
29
+ </Card>
30
+
31
+ <Card title="User Impersonation" icon="random">
32
+ Admin capability to impersonate users for support and debugging
33
+ </Card>
34
+
35
+ <Card title="OAuth 2.0 / OIDC" icon="seti:graphql">
36
+ Standard OAuth 2.0 Authorization Code flow with OpenID Connect
37
+ </Card>
38
+ </CardGrid>
39
+
40
+ ---
41
+
42
+ ## JWT-Based Authentication
43
+
44
+ ### JWT Token Structure
45
+
46
+ The platform uses JWT (JSON Web Tokens) as the primary session mechanism.
47
+
48
+ **JWT Payload**:
49
+
50
+ ```typescript
51
+ interface JWTPayload {
52
+ // User identity
53
+ id: string; // User ID
54
+ username: string; // Email or username
55
+ displayName: string; // Full name
56
+ authProviderName: string; // IDP name (okta, entraid, etc.)
57
+
58
+ // Authorization
59
+ groups: string[]; // User's groups
60
+ applications: string[]; // Allowed application IDs
61
+
62
+ // Optional fields
63
+ orig?: any; // Original user object from IDP
64
+ impersonatedBy?: { // If user is being impersonated
65
+ username: string;
66
+ displayName: string;
67
+ orig?: any;
68
+ };
69
+
70
+ // Standard JWT claims
71
+ iat: number; // Issued at
72
+ exp: number; // Expiration
73
+ iss: string; // Issuer
74
+ aud: string; // Audience
75
+ }
76
+ ```
77
+
78
+ **Example JWT**:
79
+
80
+ ```json
81
+ {
82
+ "id": "user-abc-123",
83
+ "username": "john.doe@acme.com",
84
+ "displayName": "John Doe",
85
+ "authProviderName": "okta",
86
+ "groups": ["admins", "users"],
87
+ "applications": ["app-1", "app-2", "app-3"],
88
+ "iat": 1704067200,
89
+ "exp": 1704153600,
90
+ "iss": "https://platform.acme.com",
91
+ "aud": "platform-users"
92
+ }
93
+ ```
94
+
95
+ ### Cookie-Based Storage
96
+
97
+ JWT tokens are stored in HTTP-only cookies for security.
98
+
99
+ **Cookie Configuration**:
100
+
101
+ ```typescript
102
+ // Set authentication cookie
103
+ response.cookie(AUTH_COOKIE_NAME, jwtToken, {
104
+ httpOnly: true, // Not accessible via JavaScript
105
+ secure: true, // Only sent over HTTPS
106
+ sameSite: 'lax', // CSRF protection
107
+ maxAge: 24 * 60 * 60 * 1000, // 24 hours
108
+ path: '/', // Available for all paths
109
+ domain: '.platform.com' // Available for all subdomains
110
+ });
111
+ ```
112
+
113
+ <Aside type="tip" title="Security Benefits">
114
+ HTTP-only cookies prevent XSS attacks since JavaScript cannot access the token. The `secure` flag ensures tokens are only sent over HTTPS. The `sameSite` attribute provides CSRF protection.
115
+ </Aside>
116
+
117
+ ### Token Validation Flow
118
+
119
+ ```
120
+ ┌──────────┐
121
+ │ Browser │
122
+ └────┬─────┘
123
+ │ GET /api/data
124
+ │ Cookie: auth_token=eyJhbGc...
125
+
126
+ ┌─────────────────────────────────┐
127
+ │ Gateway Authentication Guard │
128
+ │ │
129
+ │ 1. Extract token from cookie │
130
+ │ ↓ │
131
+ │ 2. Verify JWT signature │
132
+ │ ↓ │
133
+ │ 3. Check expiration │
134
+ │ ↓ │
135
+ │ 4. Decode payload │
136
+ │ ↓ │
137
+ │ 5. Create AuthUser object │
138
+ └────┬────────────────────────────┘
139
+ │ AuthUser { username, groups, ... }
140
+
141
+ ┌─────────────────────────────────┐
142
+ │ Store in Platform Context │
143
+ └─────────────────────────────────┘
144
+ ```
145
+
146
+ **Implementation**:
147
+
148
+ ```typescript
149
+ @Injectable()
150
+ export class AuthenticationService {
151
+ async validateToken(token: string): Promise<AuthUserWithApplications> {
152
+ // Get JWT secret (can be stored in parameters table)
153
+ const secret = await this.parametersService.getParameterOrDefault(
154
+ 'AUTHENTICATION_SESSION_JWT_SECRET',
155
+ this.configService.get<string>('jwtSecret')
156
+ );
157
+
158
+ // Verify and decode JWT
159
+ const payload = this.jwtService.verify(token, { secret });
160
+
161
+ // Map to AuthUser
162
+ return {
163
+ id: payload.id,
164
+ username: payload.username,
165
+ displayName: payload.displayName,
166
+ authProviderName: payload.authProviderName,
167
+ groups: payload.groups || [],
168
+ applications: payload.applications || [],
169
+ impersonatedBy: payload.impersonatedBy,
170
+ orig: payload.orig
171
+ };
172
+ }
173
+ }
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Multi-IDP Architecture
179
+
180
+ The platform supports multiple identity providers through a pluggable architecture.
181
+
182
+ ### Supported Identity Providers
183
+
184
+ <Tabs>
185
+ <TabItem label="Okta">
186
+ **Configuration**:
187
+ ```bash
188
+ OKTA_DOMAIN=dev-12345.okta.com
189
+ OKTA_CLIENT_ID=0oa1b2c3d4e5f6g7h8i9
190
+ OKTA_CLIENT_SECRET=secret
191
+ OKTA_REDIRECT_URI=https://platform.com/auth/okta/callback
192
+ ```
193
+
194
+ **Features**:
195
+ - User authentication via Okta
196
+ - Group synchronization from Okta groups
197
+ - Automatic user provisioning
198
+ - Support for Okta Universal Directory
199
+ </TabItem>
200
+
201
+ <TabItem label="Microsoft EntraId">
202
+ **Configuration**:
203
+ ```bash
204
+ ENTRAID_TENANT_ID=12345678-1234-1234-1234-123456789012
205
+ ENTRAID_CLIENT_ID=87654321-4321-4321-4321-210987654321
206
+ ENTRAID_CLIENT_SECRET=secret
207
+ ENTRAID_REDIRECT_URI=https://platform.com/auth/entraid/callback
208
+ ```
209
+
210
+ **Features**:
211
+ - Azure AD authentication
212
+ - Integration with Microsoft 365
213
+ - Security group synchronization
214
+ - Conditional access policy support
215
+ </TabItem>
216
+
217
+ <TabItem label="OneLogin">
218
+ **Configuration**:
219
+ ```bash
220
+ ONELOGIN_SUBDOMAIN=acme
221
+ ONELOGIN_CLIENT_ID=abc123def456
222
+ ONELOGIN_CLIENT_SECRET=secret
223
+ ONELOGIN_REDIRECT_URI=https://platform.com/auth/onelogin/callback
224
+ ONELOGIN_REGION=us
225
+ ```
226
+
227
+ **Features**:
228
+ - OneLogin SSO
229
+ - Role and group mapping
230
+ - API access for user management
231
+ </TabItem>
232
+
233
+ <TabItem label="Github">
234
+ **Configuration**:
235
+ ```bash
236
+ GITHUB_CLIENT_ID=Iv1.a1b2c3d4e5f6g7h8
237
+ GITHUB_CLIENT_SECRET=secret
238
+ GITHUB_REDIRECT_URI=https://platform.com/auth/github/callback
239
+ ```
240
+
241
+ **Features**:
242
+ - GitHub OAuth authentication
243
+ - Organization membership sync
244
+ - Team-based authorization
245
+ </TabItem>
246
+
247
+ <TabItem label="AWS Cognito">
248
+ **Configuration**:
249
+ ```bash
250
+ COGNITO_USER_POOL_ID=us-east-1_ABC123DEF
251
+ COGNITO_CLIENT_ID=1a2b3c4d5e6f7g8h9i0j1k2l3m
252
+ COGNITO_CLIENT_SECRET=secret
253
+ COGNITO_DOMAIN=acme-auth.auth.us-east-1.amazoncognito.com
254
+ COGNITO_REDIRECT_URI=https://platform.com/auth/cognito/callback
255
+ COGNITO_REGION=us-east-1
256
+ ```
257
+
258
+ **Features**:
259
+ - AWS Cognito user pools
260
+ - Custom attributes support
261
+ - MFA integration
262
+ - Lambda trigger support
263
+ </TabItem>
264
+ </Tabs>
265
+
266
+ ### OAuth Provider Architecture
267
+
268
+ **Location**: `apps/pae-nestjs-gateway-service/src/authentication/oauth-providers/`
269
+
270
+ Each provider implements a standard interface:
271
+
272
+ ```typescript
273
+ interface OAuthProvider {
274
+ // Provider metadata
275
+ name: string;
276
+ displayName: string;
277
+
278
+ // OAuth configuration
279
+ getConfig(): OAuthConfig;
280
+
281
+ // User info mapping
282
+ mapUserInfo(rawUserInfo: any): UserInfo;
283
+
284
+ // Group synchronization
285
+ getUserGroups(userId: string, accessToken: string): Promise<string[]>;
286
+ }
287
+ ```
288
+
289
+ **Provider Structure**:
290
+
291
+ ```
292
+ oauth-providers/
293
+ ├── okta/
294
+ │ ├── index.ts # Provider implementation
295
+ │ ├── provider.config.ts # OAuth configuration
296
+ │ ├── mapper.ts # User info mapping
297
+ │ ├── types.ts # TypeScript types
298
+ │ └── groups.service.ts # Group sync logic
299
+ ├── entra-id/
300
+ │ ├── index.ts
301
+ │ ├── provider.config.ts
302
+ │ ├── mapper.ts
303
+ │ └── api/ # MS Graph API integration
304
+ │ ├── get-groups.ts
305
+ │ └── get-groups-for-user.ts
306
+ ├── onelogin/
307
+ │ ├── index.ts
308
+ │ ├── provider.config.ts
309
+ │ ├── mapper.ts
310
+ │ └── api/ # OneLogin API integration
311
+ ├── github/
312
+ │ ├── index.ts
313
+ │ ├── provider.config.ts
314
+ │ └── mapper.ts
315
+ ├── cognito/
316
+ │ ├── index.ts
317
+ │ ├── provider.config.ts
318
+ │ ├── mapper.ts
319
+ │ └── api/ # Cognito API integration
320
+ └── providers.registry.ts # Provider registration
321
+ ```
322
+
323
+ ---
324
+
325
+ ## OAuth 2.0 Authorization Code Flow
326
+
327
+ The platform implements the standard OAuth 2.0 Authorization Code flow with PKCE.
328
+
329
+ ### Authentication Flow Diagram
330
+
331
+ ```
332
+ ┌─────────┐ ┌──────────┐
333
+ │ Browser │ │ IDP │
334
+ │ │ │ (Okta) │
335
+ └────┬────┘ └─────┬────┘
336
+ │ │
337
+ │ 1. User clicks "Login" │
338
+ │────────────────────────────────▶ │
339
+ │ │
340
+ │ 2. Redirect to /auth/login │
341
+ │────────────────────────────────▶ │
342
+ │ │
343
+ │ 3. Redirect to IDP login page │
344
+ │─────────────────────────────────────────────────────▶
345
+ │ GET /authorize? │
346
+ │ client_id=... │
347
+ │ &redirect_uri=... │
348
+ │ &response_type=code │
349
+ │ &scope=openid profile email │
350
+ │ &state=random-state │
351
+ │ &code_challenge=... │
352
+ │ │
353
+ │ 4. User authenticates with IDP │
354
+ │◀─────────────────────────────────────────────────────
355
+ │ (Username + Password + MFA) │
356
+ │ │
357
+ │ 5. IDP redirects back with code │
358
+ │◀─────────────────────────────────────────────────────
359
+ │ GET /auth/callback? │
360
+ │ code=auth-code-123 │
361
+ │ &state=random-state │
362
+ │ │
363
+ │ 6. Gateway receives callback │
364
+ │────────────────────────────────▶ │
365
+ │ │
366
+ │ 7. Exchange code for tokens │
367
+ │─────────────────────────────────────────────────────▶
368
+ │ POST /token │
369
+ │ code=auth-code-123 │
370
+ │ &client_id=... │
371
+ │ &client_secret=... │
372
+ │ &redirect_uri=... │
373
+ │ &code_verifier=... │
374
+ │ │
375
+ │ 8. Receive tokens │
376
+ │◀─────────────────────────────────────────────────────
377
+ │ { │
378
+ │ "access_token": "eyJhbGc...", │
379
+ │ "id_token": "eyJhbGc...", │
380
+ │ "refresh_token": "...", │
381
+ │ "expires_in": 3600 │
382
+ │ } │
383
+ │ │
384
+ │ 9. Get user info from IDP │
385
+ │─────────────────────────────────────────────────────▶
386
+ │ GET /userinfo │
387
+ │ Authorization: Bearer eyJhbGc... │
388
+ │ │
389
+ │ 10. Receive user info │
390
+ │◀─────────────────────────────────────────────────────
391
+ │ { │
392
+ │ "sub": "user-123", │
393
+ │ "email": "john@acme.com", │
394
+ │ "name": "John Doe" │
395
+ │ } │
396
+ │ │
397
+ │ 11. Create platform user (if new) │
398
+ │ 12. Get user's groups │
399
+ │ 13. Generate platform JWT │
400
+ │ 14. Set HTTP-only cookie │
401
+ │◀──────────────────────────────── │
402
+ │ Set-Cookie: auth_token=eyJhbGc... │
403
+ │ │
404
+ │ 15. Redirect to application │
405
+ │◀──────────────────────────────── │
406
+ │ Location: / │
407
+ │ │
408
+ │ 16. Access application with cookie │
409
+ │────────────────────────────────▶ │
410
+ │ │
411
+ ```
412
+
413
+ ### Implementation Details
414
+
415
+ <Tabs>
416
+ <TabItem label="1. Login Initiation">
417
+ ```typescript
418
+ // apps/pae-nestjs-gateway-service/src/authentication/authentication.controller.ts
419
+
420
+ @Get('auth/login')
421
+ async login(
422
+ @Query('provider') provider: string,
423
+ @Res() response: FastifyReply
424
+ ) {
425
+ // Get OAuth provider configuration
426
+ const oauthProvider = this.providersRegistry.getProvider(provider);
427
+ const config = oauthProvider.getConfig();
428
+
429
+ // Generate PKCE code verifier and challenge
430
+ const codeVerifier = generateCodeVerifier();
431
+ const codeChallenge = generateCodeChallenge(codeVerifier);
432
+
433
+ // Generate state for CSRF protection
434
+ const state = generateRandomState();
435
+
436
+ // Store in session
437
+ await this.sessionStore.set(state, {
438
+ codeVerifier,
439
+ provider,
440
+ returnTo: request.query.returnTo
441
+ });
442
+
443
+ // Build authorization URL
444
+ const authUrl = new URL(config.authorizationEndpoint);
445
+ authUrl.searchParams.set('client_id', config.clientId);
446
+ authUrl.searchParams.set('redirect_uri', config.redirectUri);
447
+ authUrl.searchParams.set('response_type', 'code');
448
+ authUrl.searchParams.set('scope', config.scope);
449
+ authUrl.searchParams.set('state', state);
450
+ authUrl.searchParams.set('code_challenge', codeChallenge);
451
+ authUrl.searchParams.set('code_challenge_method', 'S256');
452
+
453
+ // Redirect to IDP
454
+ response.redirect(authUrl.toString());
455
+ }
456
+ ```
457
+ </TabItem>
458
+
459
+ <TabItem label="2. Callback Handling">
460
+ ```typescript
461
+ @Get('auth/callback')
462
+ async callback(
463
+ @Query('code') code: string,
464
+ @Query('state') state: string,
465
+ @Res() response: FastifyReply
466
+ ) {
467
+ // Retrieve session data
468
+ const session = await this.sessionStore.get(state);
469
+ if (!session) {
470
+ throw new UnauthorizedException('Invalid state');
471
+ }
472
+
473
+ // Get provider
474
+ const oauthProvider = this.providersRegistry.getProvider(session.provider);
475
+ const config = oauthProvider.getConfig();
476
+
477
+ // Exchange code for tokens
478
+ const tokenResponse = await axios.post(config.tokenEndpoint, {
479
+ grant_type: 'authorization_code',
480
+ code,
481
+ client_id: config.clientId,
482
+ client_secret: config.clientSecret,
483
+ redirect_uri: config.redirectUri,
484
+ code_verifier: session.codeVerifier
485
+ });
486
+
487
+ const { access_token, id_token, refresh_token } = tokenResponse.data;
488
+
489
+ // Get user info
490
+ const userInfoResponse = await axios.get(config.userInfoEndpoint, {
491
+ headers: {
492
+ Authorization: `Bearer ${access_token}`
493
+ }
494
+ });
495
+
496
+ // Map to platform user
497
+ const userInfo = oauthProvider.mapUserInfo(userInfoResponse.data);
498
+
499
+ // Continue with user creation/update...
500
+ }
501
+ ```
502
+ </TabItem>
503
+
504
+ <TabItem label="3. User Provisioning">
505
+ ```typescript
506
+ // Create or update user in platform database
507
+ let user = await this.usersService.findByEmail(userInfo.email);
508
+
509
+ if (!user) {
510
+ // Create new user
511
+ user = await this.usersService.create({
512
+ email: userInfo.email,
513
+ displayName: userInfo.name,
514
+ authProviderName: session.provider,
515
+ authProviderUserId: userInfo.id,
516
+ tenantId: userInfo.tenantId // Determined from email domain or IDP config
517
+ });
518
+ } else {
519
+ // Update existing user
520
+ await this.usersService.update(user.id, {
521
+ displayName: userInfo.name,
522
+ lastLoginAt: new Date()
523
+ });
524
+ }
525
+
526
+ // Sync groups from IDP
527
+ const groups = await oauthProvider.getUserGroups(userInfo.id, access_token);
528
+ await this.groupsService.syncUserGroups(user.id, groups);
529
+
530
+ // Get user's allowed applications
531
+ const applications = await this.authzService.getAllowedApplications(user.username);
532
+
533
+ // Generate platform JWT
534
+ const jwtPayload = {
535
+ id: user.id,
536
+ username: user.email,
537
+ displayName: user.displayName,
538
+ authProviderName: session.provider,
539
+ groups,
540
+ applications: applications.map(app => app.id)
541
+ };
542
+
543
+ const jwtToken = this.jwtService.sign(jwtPayload);
544
+
545
+ // Set HTTP-only cookie
546
+ response.cookie('auth_token', jwtToken, {
547
+ httpOnly: true,
548
+ secure: true,
549
+ sameSite: 'lax',
550
+ maxAge: 24 * 60 * 60 * 1000
551
+ });
552
+
553
+ // Redirect to application
554
+ response.redirect(session.returnTo || '/');
555
+ ```
556
+ </TabItem>
557
+ </Tabs>
558
+
559
+ <Aside type="note" title="PKCE Security">
560
+ The platform uses PKCE (Proof Key for Code Exchange) to protect against authorization code interception attacks. The `code_challenge` is sent during authorization, and the `code_verifier` is sent during token exchange, preventing attackers from using stolen authorization codes.
561
+ </Aside>
562
+
563
+ ---
564
+
565
+ ## API Key Authentication
566
+
567
+ API keys provide long-lived authentication for programmatic access.
568
+
569
+ ### API Key Structure
570
+
571
+ ```typescript
572
+ interface ApiKey {
573
+ id: string;
574
+ name: string; // Human-readable name
575
+ key: string; // The actual key (hashed in DB)
576
+ userId: string; // Associated user
577
+ tenantId: string; // Tenant scope
578
+ permissions: string[]; // Allowed operations
579
+ expiresAt?: Date; // Optional expiration
580
+ lastUsedAt?: Date; // Track usage
581
+ createdAt: Date;
582
+ createdBy: string;
583
+ }
584
+ ```
585
+
586
+ ### API Key Generation
587
+
588
+ ```typescript
589
+ @Post('api-keys')
590
+ async createApiKey(
591
+ @Body() dto: CreateApiKeyDto,
592
+ @User() user: AuthUserWithApplications
593
+ ) {
594
+ // Generate secure random key
595
+ const apiKey = crypto.randomBytes(32).toString('base64url');
596
+
597
+ // Hash for storage (using bcrypt)
598
+ const hashedKey = await bcrypt.hash(apiKey, 10);
599
+
600
+ // Store in database
601
+ await this.apiKeysService.create({
602
+ name: dto.name,
603
+ key: hashedKey,
604
+ userId: user.id,
605
+ tenantId: user.tenantId,
606
+ permissions: dto.permissions,
607
+ expiresAt: dto.expiresAt
608
+ });
609
+
610
+ // Return key ONCE (cannot be retrieved again)
611
+ return {
612
+ apiKey,
613
+ message: 'Save this key securely - it cannot be retrieved again'
614
+ };
615
+ }
616
+ ```
617
+
618
+ ### API Key Usage
619
+
620
+ ```bash
621
+ # Use API key in Authorization header
622
+ curl -H "Authorization: Bearer pae_abc123def456ghi789jkl012mno345pqr678stu901vwx234" \
623
+ https://platform.com/api/data
624
+ ```
625
+
626
+ **Validation Flow**:
627
+
628
+ ```typescript
629
+ async validateApiKey(apiKey: string): Promise<AuthUserWithApplications | null> {
630
+ // Find API key in database
631
+ const storedKey = await this.apiKeysRepository.findByKey(apiKey);
632
+ if (!storedKey) {
633
+ return null;
634
+ }
635
+
636
+ // Verify key matches (compare hashes)
637
+ const isValid = await bcrypt.compare(apiKey, storedKey.key);
638
+ if (!isValid) {
639
+ return null;
640
+ }
641
+
642
+ // Check expiration
643
+ if (storedKey.expiresAt && storedKey.expiresAt < new Date()) {
644
+ return null;
645
+ }
646
+
647
+ // Update last used
648
+ await this.apiKeysRepository.updateLastUsed(storedKey.id);
649
+
650
+ // Load user
651
+ const user = await this.usersService.findById(storedKey.userId);
652
+
653
+ // Return user context with API key permissions
654
+ return {
655
+ id: user.id,
656
+ username: user.email,
657
+ displayName: user.displayName,
658
+ authProviderName: 'api-key',
659
+ groups: user.groups,
660
+ applications: storedKey.permissions
661
+ };
662
+ }
663
+ ```
664
+
665
+ ---
666
+
667
+ ## Service-to-Service Authentication
668
+
669
+ Services authenticate with each other using a shared secret.
670
+
671
+ ### Configuration
672
+
673
+ ```bash
674
+ # Set the same secret on all services
675
+ SERVICE_ACCESS_SECRET=shared-secret-for-internal-services
676
+ ```
677
+
678
+ ### Usage
679
+
680
+ ```typescript
681
+ // Service A calling Service B
682
+ const response = await axios.get('http://service-b/api/data', {
683
+ headers: {
684
+ 'Authorization': `Bearer ${process.env.SERVICE_ACCESS_SECRET}`,
685
+ 'x-forwarded-user': 'service-a' // Optional: identify the calling service
686
+ }
687
+ });
688
+ ```
689
+
690
+ ### Validation
691
+
692
+ ```typescript
693
+ async validateApiKey(apiKey: string, req: FastifyRequest["raw"]): Promise<AuthUserWithApplications | null> {
694
+ // Check if it's the service access secret
695
+ if (apiKey === process.env.SERVICE_ACCESS_SECRET) {
696
+ // Extract calling service name
697
+ const author = req.headers['x-forwarded-user'] as string | undefined;
698
+
699
+ // Return service account
700
+ return {
701
+ id: 'service',
702
+ username: author ?? 'service',
703
+ displayName: 'Service Account',
704
+ authProviderName: 'service-access',
705
+ groups: [],
706
+ applications: [] // Services typically have full access
707
+ };
708
+ }
709
+
710
+ // Otherwise, check if it's a regular API key
711
+ return this.apiKeysService.getUserForApiKey(apiKey);
712
+ }
713
+ ```
714
+
715
+ <Aside type="caution" title="Security Warning">
716
+ The `SERVICE_ACCESS_SECRET` grants full platform access. Rotate it regularly and never expose it in logs or client-side code. Use it only for internal service-to-service communication.
717
+ </Aside>
718
+
719
+ ---
720
+
721
+ ## User Impersonation
722
+
723
+ Admins can impersonate users for support and debugging purposes.
724
+
725
+ ### Impersonation Configuration
726
+
727
+ ```typescript
728
+ // Database: impersonation_configs table
729
+ interface ImpersonationConfig {
730
+ id: string;
731
+ username: string; // Admin who can impersonate
732
+ restrictedTo: string[]; // Optional: limit to specific users
733
+ createdAt: Date;
734
+ createdBy: string;
735
+ }
736
+ ```
737
+
738
+ ### Impersonation Flow
739
+
740
+ ```
741
+ 1. Admin requests to impersonate user
742
+
743
+ 2. Check if admin has impersonation permission
744
+
745
+ 3. Check if target user is in restricted list (if applicable)
746
+
747
+ 4. Create new JWT with impersonation data
748
+
749
+ 5. Set impersonation mode cookie
750
+
751
+ 6. All requests now use impersonated user's context
752
+
753
+ 7. Admin can stop impersonation to return to their account
754
+ ```
755
+
756
+ **Implementation**:
757
+
758
+ ```typescript
759
+ @Post('impersonate')
760
+ async startImpersonation(
761
+ @Body() dto: { targetUsername: string },
762
+ @User() admin: AuthUserWithApplications
763
+ ) {
764
+ // Check if admin can impersonate
765
+ const config = await this.impersonationService.getConfig(admin.username);
766
+ if (!config) {
767
+ throw new ForbiddenException('You do not have impersonation permission');
768
+ }
769
+
770
+ // Check if target user is allowed
771
+ if (config.restrictedTo.length > 0 && !config.restrictedTo.includes(dto.targetUsername)) {
772
+ throw new ForbiddenException('You cannot impersonate this user');
773
+ }
774
+
775
+ // Load target user
776
+ const targetUser = await this.usersService.findByUsername(dto.targetUsername);
777
+ if (!targetUser) {
778
+ throw new NotFoundException('User not found');
779
+ }
780
+
781
+ // Create JWT with impersonation
782
+ const jwtPayload = {
783
+ ...targetUser,
784
+ impersonatedBy: {
785
+ username: admin.username,
786
+ displayName: admin.displayName,
787
+ orig: admin.orig
788
+ }
789
+ };
790
+
791
+ const jwtToken = this.jwtService.sign(jwtPayload);
792
+
793
+ // Set cookies
794
+ response.cookie('auth_token', jwtToken, { httpOnly: true, secure: true });
795
+ response.cookie('impersonation_mode', 'true', { httpOnly: false }); // Client needs to read this
796
+
797
+ return {
798
+ message: 'Impersonation started',
799
+ targetUser: dto.targetUsername
800
+ };
801
+ }
802
+ ```
803
+
804
+ <Aside type="tip" title="Audit Trail">
805
+ All actions performed during impersonation are logged with both the impersonated user and the admin who initiated impersonation. This ensures accountability and compliance.
806
+ </Aside>
807
+
808
+ ---
809
+
810
+ ## Session Management
811
+
812
+ ### Session States
813
+
814
+ The platform tracks three session states:
815
+
816
+ ```typescript
817
+ type SessionStatus = 'valid' | 'expired' | 'invalid';
818
+
819
+ async getSessionStatus(token: string): Promise<SessionStatus> {
820
+ if (!token) return 'invalid';
821
+
822
+ try {
823
+ await this.validateToken(token);
824
+ return 'valid';
825
+ } catch (error) {
826
+ if (error?.name === 'TokenExpiredError') {
827
+ return 'expired';
828
+ }
829
+ return 'invalid';
830
+ }
831
+ }
832
+ ```
833
+
834
+ ### Handling Invalid Sessions
835
+
836
+ ```typescript
837
+ handleInvalidSession(context: ExecutionContext, session: Session) {
838
+ const request = context.switchToHttp().getRequest<FastifyRequest>();
839
+ const response = context.switchToHttp().getResponse<FastifyReply>();
840
+
841
+ // Check if this is a browser request
842
+ if (this.isNoRedirectRequest(request)) {
843
+ // API/non-browser request: return 401
844
+ response.status(401).send({
845
+ message: session.status === 'expired' ? 'Session expired' : 'Unauthorized',
846
+ authRedirectURL: '/'
847
+ });
848
+ } else {
849
+ // Browser request: redirect to login
850
+ const returnTo = request.url;
851
+ response.redirect(`/auth/login?returnTo=${encodeURIComponent(returnTo)}`);
852
+ }
853
+ }
854
+ ```
855
+
856
+ ---
857
+
858
+ ## Security Considerations
859
+
860
+ <Aside type="caution" title="Authentication Security Best Practices">
861
+
862
+ **JWT Security**:
863
+ - Use strong secrets (minimum 256 bits)
864
+ - Rotate secrets regularly
865
+ - Keep tokens short-lived (< 24 hours)
866
+ - Never store JWTs in localStorage (XSS risk)
867
+
868
+ **Cookie Security**:
869
+ - Always use `httpOnly` flag
870
+ - Always use `secure` flag in production
871
+ - Use `sameSite: 'lax'` or `strict` for CSRF protection
872
+ - Set appropriate `domain` for subdomain sharing
873
+
874
+ **API Key Security**:
875
+ - Hash API keys before storage (never store plaintext)
876
+ - Use cryptographically secure random generation
877
+ - Implement rate limiting
878
+ - Support key rotation
879
+ - Monitor and alert on unusual usage
880
+
881
+ **IDP Integration**:
882
+ - Validate redirect URIs strictly
883
+ - Implement PKCE for OAuth flows
884
+ - Verify state parameter for CSRF protection
885
+ - Use short-lived authorization codes
886
+ - Store IDP tokens securely (encrypted)
887
+
888
+ **General**:
889
+ - Log all authentication events (success and failure)
890
+ - Implement account lockout after failed attempts
891
+ - Use HTTPS everywhere
892
+ - Implement session timeout
893
+ - Support MFA where possible
894
+
895
+ </Aside>
896
+
897
+ ---
898
+
899
+ ## Next Steps
900
+
901
+ - **[Authorization System](/_/docs/architecture/authorization-system/)** - Understanding RBAC and permissions
902
+ - **[Gateway Architecture](/_/docs/architecture/gateway-architecture/)** - How gateway processes authentication
903
+ - **[Multi-Tenancy](/_/docs/architecture/multi-tenancy/)** - Tenant isolation and user management