@lenne.tech/nest-server 11.10.2 → 11.10.3

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 (56) hide show
  1. package/dist/config.env.js +16 -133
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/interfaces/server-options.interface.d.ts +4 -0
  4. package/dist/core/modules/better-auth/better-auth.config.d.ts +3 -0
  5. package/dist/core/modules/better-auth/better-auth.config.js +176 -47
  6. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  7. package/dist/core/modules/better-auth/core-better-auth-api.middleware.d.ts +5 -1
  8. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +101 -8
  9. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
  10. package/dist/core/modules/better-auth/core-better-auth-challenge.service.d.ts +20 -0
  11. package/dist/core/modules/better-auth/core-better-auth-challenge.service.js +142 -0
  12. package/dist/core/modules/better-auth/core-better-auth-challenge.service.js.map +1 -0
  13. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +1 -1
  14. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
  15. package/dist/core/modules/better-auth/core-better-auth-web.helper.d.ts +2 -0
  16. package/dist/core/modules/better-auth/core-better-auth-web.helper.js +29 -1
  17. package/dist/core/modules/better-auth/core-better-auth-web.helper.js.map +1 -1
  18. package/dist/core/modules/better-auth/core-better-auth.controller.js +5 -13
  19. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
  20. package/dist/core/modules/better-auth/core-better-auth.middleware.d.ts +0 -1
  21. package/dist/core/modules/better-auth/core-better-auth.middleware.js +6 -19
  22. package/dist/core/modules/better-auth/core-better-auth.middleware.js.map +1 -1
  23. package/dist/core/modules/better-auth/core-better-auth.module.d.ts +4 -1
  24. package/dist/core/modules/better-auth/core-better-auth.module.js +53 -19
  25. package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
  26. package/dist/core/modules/better-auth/core-better-auth.resolver.js +7 -6
  27. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
  28. package/dist/core/modules/better-auth/core-better-auth.service.d.ts +0 -2
  29. package/dist/core/modules/better-auth/core-better-auth.service.js +23 -37
  30. package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
  31. package/dist/core.module.js +3 -0
  32. package/dist/core.module.js.map +1 -1
  33. package/dist/server/modules/better-auth/better-auth.module.d.ts +4 -1
  34. package/dist/server/modules/better-auth/better-auth.module.js +4 -1
  35. package/dist/server/modules/better-auth/better-auth.module.js.map +1 -1
  36. package/dist/server/server.module.js +1 -4
  37. package/dist/server/server.module.js.map +1 -1
  38. package/dist/tsconfig.build.tsbuildinfo +1 -1
  39. package/package.json +1 -1
  40. package/src/config.env.ts +24 -174
  41. package/src/core/common/interfaces/server-options.interface.ts +288 -35
  42. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +82 -56
  43. package/src/core/modules/better-auth/README.md +132 -35
  44. package/src/core/modules/better-auth/better-auth.config.ts +402 -70
  45. package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +158 -18
  46. package/src/core/modules/better-auth/core-better-auth-challenge.service.ts +254 -0
  47. package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +1 -1
  48. package/src/core/modules/better-auth/core-better-auth-web.helper.ts +64 -1
  49. package/src/core/modules/better-auth/core-better-auth.controller.ts +6 -14
  50. package/src/core/modules/better-auth/core-better-auth.middleware.ts +7 -20
  51. package/src/core/modules/better-auth/core-better-auth.module.ts +135 -25
  52. package/src/core/modules/better-auth/core-better-auth.resolver.ts +7 -6
  53. package/src/core/modules/better-auth/core-better-auth.service.ts +27 -48
  54. package/src/core.module.ts +5 -0
  55. package/src/server/modules/better-auth/better-auth.module.ts +40 -10
  56. package/src/server/server.module.ts +2 -4
@@ -132,7 +132,15 @@ export interface IAuthLegacyEndpoints {
132
132
  *
133
133
  * Check migration status via the `betterAuthMigrationStatus` query.
134
134
  *
135
+ * **Environment Variable:** `LEGACY_AUTH_ENABLED`
136
+ *
135
137
  * @default true
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * // Via environment variable
142
+ * enabled: process.env.LEGACY_AUTH_ENABLED !== 'false',
143
+ * ```
136
144
  */
137
145
  enabled?: boolean;
138
146
 
@@ -225,11 +233,15 @@ export type IBetterAuth = IBetterAuthWithoutPasskey | IBetterAuthWithPasskey;
225
233
 
226
234
  /**
227
235
  * JWT plugin configuration for Better-Auth
236
+ *
237
+ * **Enabled by Default:** JWT is enabled by default when BetterAuth is active.
238
+ * This provides stateless authentication for API clients.
239
+ * Set `jwt: false` or `jwt: { enabled: false }` to disable.
228
240
  */
229
241
  export interface IBetterAuthJwtConfig {
230
242
  /**
231
243
  * Whether JWT plugin is enabled.
232
- * @default true (when config block is present)
244
+ * @default true (enabled by default when BetterAuth is active)
233
245
  */
234
246
  enabled?: boolean;
235
247
 
@@ -242,7 +254,36 @@ export interface IBetterAuthJwtConfig {
242
254
 
243
255
  /**
244
256
  * Passkey/WebAuthn plugin configuration for Better-Auth
257
+ *
258
+ * **Auto-Detection from baseUrl:** When `passkey: true` is set and `baseUrl` is configured,
259
+ * the following values are auto-detected:
260
+ * - `rpId`: Derived from baseUrl hostname (e.g., 'example.com')
261
+ * - `origin`: Derived from baseUrl (e.g., 'https://api.example.com')
262
+ * - `trustedOrigins`: Derived from baseUrl (e.g., ['https://api.example.com'])
263
+ *
264
+ * **Graceful Degradation:** If auto-detection fails and values are not explicitly set,
265
+ * Passkey is automatically disabled with a warning. Other auth methods continue to work.
266
+ *
245
267
  * @see https://www.better-auth.com/docs/plugins/passkey
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * // RECOMMENDED: Use auto-detection
272
+ * betterAuth: {
273
+ * baseUrl: process.env.BASE_URL, // e.g., 'https://api.example.com'
274
+ * passkey: true, // Auto-detects rpId, origin, trustedOrigins
275
+ * }
276
+ *
277
+ * // Explicit configuration (overrides auto-detection)
278
+ * betterAuth: {
279
+ * passkey: {
280
+ * rpId: 'example.com',
281
+ * origin: 'https://app.example.com',
282
+ * rpName: 'My App',
283
+ * },
284
+ * trustedOrigins: ['https://app.example.com'],
285
+ * }
286
+ * ```
246
287
  */
247
288
  export interface IBetterAuthPasskeyConfig {
248
289
  /**
@@ -254,6 +295,27 @@ export interface IBetterAuthPasskeyConfig {
254
295
  */
255
296
  authenticatorAttachment?: 'cross-platform' | 'platform';
256
297
 
298
+ /**
299
+ * Where to store WebAuthn challenges.
300
+ * - 'database': Store in MongoDB with TTL (default, works everywhere including cross-origin and JWT mode)
301
+ * - 'cookie': Store in httpOnly cookie (requires session cookies and same-origin setup)
302
+ *
303
+ * Use 'cookie' only when:
304
+ * - You want to avoid database writes for challenges
305
+ * - You have a same-origin setup (frontend and API on same domain/port)
306
+ * - You are NOT using JWT mode
307
+ *
308
+ * @default 'database'
309
+ */
310
+ challengeStorage?: 'cookie' | 'database';
311
+
312
+ /**
313
+ * TTL in seconds for database-stored challenges.
314
+ * Only used when challengeStorage is 'database'.
315
+ * @default 300 (5 minutes)
316
+ */
317
+ challengeTtlSeconds?: number;
318
+
257
319
  /**
258
320
  * Whether passkey authentication is enabled.
259
321
  * @default true (when config block is present)
@@ -261,8 +323,11 @@ export interface IBetterAuthPasskeyConfig {
261
323
  enabled?: boolean;
262
324
 
263
325
  /**
264
- * Origin URL for WebAuthn
265
- * e.g. 'http://localhost:3000'
326
+ * Origin URL for WebAuthn.
327
+ *
328
+ * **Auto-detected** from `baseUrl` if not set (e.g., 'https://api.example.com').
329
+ *
330
+ * @example 'http://localhost:3000' or 'https://api.example.com'
266
331
  */
267
332
  origin?: string;
268
333
 
@@ -276,8 +341,11 @@ export interface IBetterAuthPasskeyConfig {
276
341
  residentKey?: 'discouraged' | 'preferred' | 'required';
277
342
 
278
343
  /**
279
- * Relying Party ID (usually the domain)
280
- * e.g. 'localhost' or 'example.com'
344
+ * Relying Party ID (usually the domain without protocol).
345
+ *
346
+ * **Auto-detected** from `baseUrl` hostname if not set (e.g., 'example.com').
347
+ *
348
+ * @example 'localhost' or 'example.com'
281
349
  */
282
350
  rpId?: string;
283
351
 
@@ -298,6 +366,7 @@ export interface IBetterAuthPasskeyConfig {
298
366
 
299
367
  /**
300
368
  * Custom cookie name for WebAuthn challenge storage.
369
+ * Only used when challengeStorage is 'cookie'.
301
370
  * @default 'better-auth-passkey'
302
371
  */
303
372
  webAuthnChallengeCookie?: string;
@@ -305,17 +374,30 @@ export interface IBetterAuthPasskeyConfig {
305
374
 
306
375
  /**
307
376
  * Interface for Better-Auth rate limiting configuration
377
+ *
378
+ * **Environment Variables:**
379
+ * - `RATE_LIMIT_ENABLED` - Set to 'false' to disable
380
+ * - `RATE_LIMIT_MAX` - Maximum requests per window
381
+ * - `RATE_LIMIT_WINDOW_SECONDS` - Window duration
382
+ *
383
+ * @example
384
+ * ```typescript
385
+ * rateLimit: {
386
+ * enabled: process.env.RATE_LIMIT_ENABLED !== 'false',
387
+ * max: parseInt(process.env.RATE_LIMIT_MAX || '10', 10),
388
+ * },
389
+ * ```
308
390
  */
309
391
  export interface IBetterAuthRateLimit {
310
392
  /**
311
393
  * Whether rate limiting is enabled
312
- * default: false
394
+ * @default false
313
395
  */
314
396
  enabled?: boolean;
315
397
 
316
398
  /**
317
399
  * Maximum number of requests within the time window
318
- * default: 10
400
+ * @default 10
319
401
  */
320
402
  max?: number;
321
403
 
@@ -351,10 +433,18 @@ export interface IBetterAuthRateLimit {
351
433
  * both `clientId` and `clientSecret` are provided. You only need to set
352
434
  * `enabled: false` to explicitly disable a configured provider.
353
435
  *
436
+ * **Environment Variables (convention):**
437
+ * - `SOCIAL_GOOGLE_CLIENT_ID`, `SOCIAL_GOOGLE_CLIENT_SECRET`
438
+ * - `SOCIAL_GITHUB_CLIENT_ID`, `SOCIAL_GITHUB_CLIENT_SECRET`
439
+ * - `SOCIAL_APPLE_CLIENT_ID`, `SOCIAL_APPLE_CLIENT_SECRET`
440
+ *
354
441
  * @example
355
442
  * ```typescript
356
- * // Provider is enabled (has credentials, no explicit enabled flag needed)
357
- * google: { clientId: '...', clientSecret: '...' }
443
+ * // Via environment variables (recommended)
444
+ * google: {
445
+ * clientId: process.env.SOCIAL_GOOGLE_CLIENT_ID || '',
446
+ * clientSecret: process.env.SOCIAL_GOOGLE_CLIENT_SECRET || '',
447
+ * },
358
448
  *
359
449
  * // Provider is explicitly disabled despite having credentials
360
450
  * github: { clientId: '...', clientSecret: '...', enabled: false }
@@ -382,17 +472,30 @@ export interface IBetterAuthSocialProvider {
382
472
 
383
473
  /**
384
474
  * Two-factor authentication plugin configuration for Better-Auth
475
+ *
476
+ * **Enabled by Default:** 2FA is enabled by default when BetterAuth is active.
477
+ * Users can optionally set up 2FA for their accounts.
478
+ * Set `twoFactor: false` or `twoFactor: { enabled: false }` to disable.
479
+ *
480
+ * **Environment Variables:**
481
+ * - `TWO_FACTOR_APP_NAME` - App name shown in authenticator apps
482
+ * - `TWO_FACTOR_ENABLED` - Set to 'true' to enable (default: true)
385
483
  */
386
484
  export interface IBetterAuthTwoFactorConfig {
387
485
  /**
388
- * App name shown in authenticator apps
389
- * e.g. 'My Application'
486
+ * App name shown in authenticator apps.
487
+ * This appears in Google Authenticator, Authy, etc.
488
+ *
489
+ * **Environment Variable:** `TWO_FACTOR_APP_NAME`
490
+ *
491
+ * @default 'Nest Server'
492
+ * @example 'My Application'
390
493
  */
391
494
  appName?: string;
392
495
 
393
496
  /**
394
497
  * Whether 2FA is enabled.
395
- * @default true (when config block is present)
498
+ * @default true (enabled by default when BetterAuth is active)
396
499
  */
397
500
  enabled?: boolean;
398
501
  }
@@ -533,6 +636,44 @@ export interface IJwt {
533
636
  * Options for the server
534
637
  */
535
638
  export interface IServerOptions {
639
+ /**
640
+ * Base URL of the frontend/app application.
641
+ *
642
+ * Used for:
643
+ * - CORS `trustedOrigins` configuration
644
+ * - Passkey/WebAuthn `origin` (where the browser runs)
645
+ * - Frontend redirect URLs
646
+ *
647
+ * **Auto-Detection from `baseUrl`:**
648
+ * If not set, `appUrl` is derived from `baseUrl`:
649
+ * - `https://api.example.com` → `https://example.com` (removes 'api.' prefix)
650
+ * - `https://example.com` → `https://example.com` (unchanged)
651
+ *
652
+ * **Localhost Environment Defaults:**
653
+ * When `env` is 'local', 'ci', or 'e2e' and neither `baseUrl` nor `appUrl` is set:
654
+ * - `appUrl` defaults to `http://localhost:3001`
655
+ *
656
+ * **Environment Variable:** `APP_URL` (only needed if not auto-derivable from `BASE_URL`)
657
+ *
658
+ * @example 'https://example.com' or 'http://localhost:3001'
659
+ *
660
+ * @example
661
+ * ```typescript
662
+ * // Typical production setup (appUrl auto-derived from baseUrl)
663
+ * baseUrl: process.env.BASE_URL, // e.g., 'https://api.example.com'
664
+ * // → appUrl auto-derived: 'https://example.com'
665
+ *
666
+ * // Explicit appUrl (when frontend is on different domain)
667
+ * appUrl: process.env.APP_URL, // e.g., 'https://app.different-domain.com'
668
+ *
669
+ * // Local/CI/E2E (auto-defaults)
670
+ * env: 'local', // or 'ci' or 'e2e'
671
+ * // baseUrl defaults to 'http://localhost:3000'
672
+ * // appUrl defaults to 'http://localhost:3001'
673
+ * ```
674
+ */
675
+ appUrl?: string;
676
+
536
677
  /**
537
678
  * Authentication system configuration
538
679
  *
@@ -553,21 +694,87 @@ export interface IServerOptions {
553
694
  */
554
695
  automaticObjectIdFiltering?: boolean;
555
696
 
697
+ /**
698
+ * Base URL of the API server.
699
+ *
700
+ * Used for:
701
+ * - Email links (password reset, verification)
702
+ * - OAuth callback URLs
703
+ * - Swagger/OpenAPI documentation
704
+ * - BetterAuth configuration
705
+ *
706
+ * **Localhost Environment Defaults:**
707
+ * When `env` is 'local', 'ci', or 'e2e' and `baseUrl` is not set:
708
+ * - `baseUrl` defaults to `http://localhost:3000`
709
+ *
710
+ * **Relationship with `appUrl`:**
711
+ * If `appUrl` is not set, it is auto-derived from `baseUrl`:
712
+ * - `https://api.example.com` → `appUrl: https://example.com`
713
+ * - `https://example.com` → `appUrl: https://example.com`
714
+ *
715
+ * **Environment Variable:** `BASE_URL`
716
+ *
717
+ * @example 'https://api.example.com' or 'http://localhost:3000'
718
+ *
719
+ * @example
720
+ * ```typescript
721
+ * // Production (via environment variable)
722
+ * baseUrl: process.env.BASE_URL,
723
+ *
724
+ * // Local/CI/E2E (auto-defaults)
725
+ * env: 'local', // or 'ci' or 'e2e'
726
+ * // baseUrl defaults to 'http://localhost:3000'
727
+ * ```
728
+ */
729
+ baseUrl?: string;
730
+
556
731
  /**
557
732
  * Configuration for better-auth authentication framework.
558
733
  * See: https://better-auth.com
559
734
  *
735
+ * **Zero-Config Philosophy:** BetterAuth is enabled by default.
736
+ * JWT, 2FA, and Passkey are also enabled by default when BetterAuth is active.
737
+ *
738
+ * **Passkey Auto-Activation:**
739
+ * Passkey is automatically enabled when `baseUrl` (or `appUrl`) is configured.
740
+ * If URLs are not set, Passkey is disabled with a warning (Graceful Degradation).
741
+ *
560
742
  * Accepts:
561
- * - `true`: Enable with all defaults (including JWT)
743
+ * - `undefined`: Enabled with defaults (zero-config)
744
+ * - `true`: Enable with all defaults (same as undefined)
562
745
  * - `false`: Disable BetterAuth completely
563
746
  * - `{ ... }`: Enable with custom configuration
564
- * - `undefined`: Disabled (default for backward compatibility)
747
+ * - `{ enabled: false }`: Disable BetterAuth completely
748
+ *
749
+ * | Configuration | BetterAuth | JWT | 2FA | Passkey |
750
+ * |---------------|:----------:|:---:|:---:|:-------:|
751
+ * | *not set* + no URLs | ✅ | ✅ | ✅ | ⚠️ disabled |
752
+ * | *not set* + `baseUrl` set | ✅ | ✅ | ✅ | ✅ auto |
753
+ * | `env: 'local'/'ci'/'e2e'` (auto URLs) | ✅ | ✅ | ✅ | ✅ auto |
754
+ * | `false` | ❌ | ❌ | ❌ | ❌ |
755
+ * | `{ passkey: false }` | ✅ | ✅ | ✅ | ❌ |
756
+ * | `{ twoFactor: false }` | ✅ | ✅ | ❌ | ✅ auto |
757
+ *
758
+ * @default undefined (enabled with defaults)
565
759
  *
566
760
  * @example
567
761
  * ```typescript
568
- * betterAuth: true, // Enable with defaults (JWT enabled)
569
- * betterAuth: { baseUrl: 'https://example.com' }, // Custom config
570
- * betterAuth: false, // Explicitly disabled
762
+ * // Zero-config for local/ci/e2e:
763
+ * env: 'local', // or 'ci' or 'e2e'
764
+ * // baseUrl: 'http://localhost:3000' (auto)
765
+ * // → appUrl: 'http://localhost:3001' (auto)
766
+ * // → BetterAuth + JWT + 2FA + Passkey all enabled!
767
+ *
768
+ * // Production with auto-derived appUrl:
769
+ * baseUrl: 'https://api.example.com',
770
+ * // → appUrl: 'https://example.com' (auto-derived)
771
+ * // → Passkey uses appUrl for origin/trustedOrigins
772
+ *
773
+ * // Disable Passkey explicitly (no warning):
774
+ * betterAuth: { passkey: false },
775
+ *
776
+ * // Disable BetterAuth completely:
777
+ * betterAuth: false,
571
778
  * ```
572
779
  */
573
780
  betterAuth?: boolean | IBetterAuth;
@@ -1293,28 +1500,31 @@ interface IBetterAuthBase {
1293
1500
 
1294
1501
  /**
1295
1502
  * Whether better-auth is enabled.
1296
- * BetterAuth is enabled by default (zero-config philosophy).
1297
- * Set to false to explicitly disable it.
1298
- * @default true
1503
+ *
1504
+ * **Zero-Config Philosophy:** BetterAuth is enabled by default.
1505
+ * Set to `false` to explicitly disable it.
1506
+ *
1507
+ * @default true (enabled by default)
1299
1508
  */
1300
1509
  enabled?: boolean;
1301
1510
 
1302
1511
  /**
1303
1512
  * JWT plugin configuration for API clients.
1304
1513
  *
1305
- * **Default: Enabled** - JWT is enabled by default when BetterAuth is enabled.
1306
- * This ensures a minimal config (`betterAuth: true`) provides full functionality.
1514
+ * **Enabled by Default:** JWT is enabled by default when BetterAuth is active.
1515
+ * This provides stateless authentication for API clients.
1307
1516
  *
1308
1517
  * Accepts:
1309
- * - `true` or `{}`: Enable with defaults (same as not specifying)
1518
+ * - `undefined`: Enabled with defaults (zero-config)
1519
+ * - `true` or `{}`: Enable with defaults (same as undefined)
1310
1520
  * - `{ expiresIn: '1h' }`: Enable with custom settings
1311
1521
  * - `false` or `{ enabled: false }`: Explicitly disable
1312
- * - `undefined`: Enabled with defaults (JWT is on by default)
1522
+ *
1523
+ * @default undefined (enabled by default)
1313
1524
  *
1314
1525
  * @example
1315
1526
  * ```typescript
1316
- * // JWT is enabled by default, no config needed
1317
- * betterAuth: true,
1527
+ * // JWT enabled by default - no config needed
1318
1528
  *
1319
1529
  * // Customize JWT expiry
1320
1530
  * betterAuth: { jwt: { expiresIn: '1h' } },
@@ -1378,8 +1588,46 @@ interface IBetterAuthBase {
1378
1588
  rateLimit?: IBetterAuthRateLimit;
1379
1589
 
1380
1590
  /**
1381
- * Secret for better-auth (min 32 characters)
1382
- * Used for session encryption
1591
+ * Secret for better-auth session cookie signing.
1592
+ *
1593
+ * **Used for:**
1594
+ * - Session cookie integrity (HMAC-SHA256 signature)
1595
+ * - Cookie encryption (when using JWE strategy)
1596
+ *
1597
+ * **NOT used for:**
1598
+ * - JWT signing (JWT plugin generates asymmetric keys stored in `jwks` collection)
1599
+ * - Refresh tokens (Better-Auth uses DB sessions, not refresh JWTs)
1600
+ *
1601
+ * **REQUIRED for Production!** Without a persistent secret:
1602
+ * - Session cookies become invalid on server restart
1603
+ * - Sessions cannot be shared across cluster instances
1604
+ *
1605
+ * **Note:** Unlike Legacy Auth, Better-Auth sessions are stored in the database.
1606
+ * The secret only signs the session cookie, not the session itself.
1607
+ *
1608
+ * **Minimum:** 32 characters
1609
+ *
1610
+ * **Fallback Chain (nest-server implementation):**
1611
+ * 1. `betterAuth.secret` (if set)
1612
+ * 2. `jwt.secret` (if ≥32 chars, for backwards compatibility)
1613
+ * 3. `jwt.refresh.secret` (if ≥32 chars, for backwards compatibility)
1614
+ * 4. Auto-generated (with warning, not persistent!)
1615
+ *
1616
+ * **Environment Variable:** `BETTER_AUTH_SECRET`
1617
+ *
1618
+ * **Generate a secure secret:**
1619
+ * ```bash
1620
+ * node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
1621
+ * ```
1622
+ *
1623
+ * @example
1624
+ * ```typescript
1625
+ * // Via environment variable (recommended)
1626
+ * secret: process.env.BETTER_AUTH_SECRET,
1627
+ *
1628
+ * // Or direct value (not recommended for production)
1629
+ * secret: 'your-32-character-minimum-secret-here',
1630
+ * ```
1383
1631
  */
1384
1632
  secret?: string;
1385
1633
 
@@ -1407,18 +1655,23 @@ interface IBetterAuthBase {
1407
1655
  /**
1408
1656
  * Two-factor authentication configuration.
1409
1657
  *
1658
+ * **Enabled by Default:** 2FA is enabled by default when BetterAuth is active.
1659
+ * Users can optionally set up 2FA for their accounts.
1660
+ *
1410
1661
  * Accepts:
1411
- * - `true` or `{}`: Enable with defaults
1662
+ * - `undefined`: Enabled with defaults (zero-config)
1663
+ * - `true` or `{}`: Enable with defaults (same as undefined)
1412
1664
  * - `{ appName: 'My App' }`: Enable with custom settings
1413
- * - `false` or `{ enabled: false }`: Disable
1414
- * - `undefined`: Disabled (default)
1665
+ * - `false` or `{ enabled: false }`: Explicitly disable
1666
+ *
1667
+ * @default undefined (enabled by default)
1415
1668
  *
1416
1669
  * @example
1417
1670
  * ```typescript
1418
- * twoFactor: true, // Enable with defaults
1419
- * twoFactor: {}, // Enable with defaults
1420
- * twoFactor: { appName: 'My App' }, // Enable with custom app name
1421
- * twoFactor: false, // Disable
1671
+ * // 2FA enabled by default - no config needed
1672
+ *
1673
+ * twoFactor: { appName: 'My App' }, // Customize app name in authenticator
1674
+ * twoFactor: false, // Explicitly disable 2FA
1422
1675
  * ```
1423
1676
  */
1424
1677
  twoFactor?: boolean | IBetterAuthTwoFactorConfig;
@@ -129,38 +129,47 @@ export class ServerModule {}
129
129
  **Modify:** `src/config.env.ts`
130
130
  **Reference:** `node_modules/@lenne.tech/nest-server/src/config.env.ts`
131
131
 
132
- #### For New Projects (IAM-Only):
132
+ #### Zero-Config (Default):
133
+ BetterAuth is **enabled by default** with JWT + 2FA. No configuration required!
134
+
133
135
  ```typescript
134
136
  const config = {
135
- // Disable Legacy Auth endpoints
137
+ // BetterAuth is automatically enabled with:
138
+ // - JWT tokens (for API clients)
139
+ // - 2FA/TOTP (users can enable it in settings)
140
+ // No betterAuth config needed!
141
+
142
+ // For new projects, disable Legacy Auth:
136
143
  auth: {
137
144
  legacyEndpoints: {
138
145
  enabled: false,
139
146
  },
140
147
  },
141
- // BetterAuth configuration (minimal - JWT enabled by default)
142
- betterAuth: true, // or betterAuth: {} for same effect
148
+ };
149
+ ```
143
150
 
144
- // OR with optional features:
145
- betterAuth: {
146
- twoFactor: {}, // Enable 2FA (opt-in)
147
- passkey: {}, // Enable Passkeys (opt-in)
148
- // JWT is already enabled by default
149
- },
151
+ #### With Passkey (auto-detected from baseUrl):
152
+ ```typescript
153
+ const config = {
154
+ // Passkey is auto-activated when URLs can be resolved
155
+ // Option 1: Set root-level baseUrl (production)
156
+ baseUrl: 'https://api.example.com', // rpId, origin, trustedOrigins auto-detected
157
+ env: 'production',
158
+
159
+ // Option 2: Use env: 'local'/'ci'/'e2e' (development)
160
+ // env: 'local', // Uses localhost defaults: API=:3000, App=:3001
150
161
  };
151
162
  ```
152
163
 
153
- #### For Existing Projects (Migration):
164
+ #### Disabling Features:
154
165
  ```typescript
155
166
  const config = {
156
- // Keep Legacy Auth endpoints enabled during migration
157
- auth: {
158
- legacyEndpoints: {
159
- enabled: true, // Default - can disable after migration
160
- },
167
+ betterAuth: {
168
+ jwt: false, // Disable JWT (use cookies only)
169
+ twoFactor: false, // Disable 2FA
161
170
  },
162
- // BetterAuth configuration (JWT enabled by default)
163
- betterAuth: true, // Minimal config, or use object for more options
171
+ // OR disable BetterAuth completely:
172
+ betterAuth: false,
164
173
  };
165
174
  ```
166
175
 
@@ -345,26 +354,31 @@ async function useBackupCode(code: string) {
345
354
 
346
355
  ### Passkey Login Flow (Client-Side)
347
356
 
348
- Handle passkey authentication with session validation fallback:
357
+ Handle passkey authentication with session validation fallback.
358
+
359
+ **IMPORTANT:** For JWT mode (`cookies: false`), you MUST use the `authenticateWithPasskey()` function from the composable instead of `authClient.signIn.passkey()` directly. This is because JWT mode requires sending a `challengeId` to the server for challenge verification.
349
360
 
350
361
  ```typescript
351
- // login.vue - Passkey login
362
+ // login.vue - Passkey login (JWT-compatible)
363
+ // Use authenticateWithPasskey from useBetterAuth composable
364
+ const { authenticateWithPasskey, setUser, validateSession } = useBetterAuth();
365
+
352
366
  async function onPasskeyLogin() {
353
367
  try {
354
- // Use official Better Auth passkey sign-in
355
- const result = await authClient.signIn.passkey();
368
+ // Use composable method which handles challengeId for JWT mode
369
+ const result = await authenticateWithPasskey();
356
370
 
357
- if (result.error) {
358
- showError(result.error.message || 'Passkey authentication failed');
371
+ // Check for error (returns { success, error?, user?, session? })
372
+ if (!result.success) {
373
+ showError(result.error || 'Passkey authentication failed');
359
374
  return;
360
375
  }
361
376
 
362
377
  // Update auth state with user data if available
363
- if (result.data?.user) {
364
- setUser(result.data.user);
365
- } else if (result.data?.session) {
366
- // IMPORTANT: Passkey auth returns session without user
367
- // Fetch user data via session validation
378
+ if (result.user) {
379
+ setUser(result.user);
380
+ } else {
381
+ // Passkey auth may return success without user - fetch via session validation
368
382
  await validateSession();
369
383
  }
370
384
 
@@ -378,33 +392,36 @@ async function onPasskeyLogin() {
378
392
  showError(err instanceof Error ? err.message : 'Passkey login failed');
379
393
  }
380
394
  }
381
-
382
- // Helper: Validate session and fetch user data
383
- async function validateSession() {
384
- const sessionResult = await authClient.$fetch('/session');
385
- if (sessionResult.user) {
386
- setUser(sessionResult.user);
387
- return true;
388
- }
389
- return false;
390
- }
391
395
  ```
392
396
 
397
+ **Why not use `authClient.signIn.passkey()` directly?**
398
+
399
+ In JWT mode (`cookies: false`), the server stores WebAuthn challenges in the database instead of cookies. The server returns a `challengeId` in the generate-options response, which must be sent back in the verify request. The `authenticateWithPasskey()` composable function handles this automatically.
400
+
401
+ | Mode | Challenge Storage | Client Approach |
402
+ |------|------------------|-----------------|
403
+ | Cookie (`cookies: true` or not set) | Cookie (`better-auth.better-auth-passkey`) | Either approach works |
404
+ | JWT (`cookies: false`) | Database with `challengeId` mapping (auto-enabled) | **Must use composable** |
405
+
406
+ **Note:** Database challenge storage is automatically enabled when `options.cookies: false` is set. No additional configuration is required.
407
+
393
408
  ### Passkey Registration (Client-Side)
394
409
 
395
- Register a new passkey for an authenticated user:
410
+ Register a new passkey for an authenticated user.
411
+
412
+ **IMPORTANT:** For JWT mode (`cookies: false`), you MUST use the `registerPasskey()` function from the composable. This ensures the `challengeId` is correctly sent to the server.
396
413
 
397
414
  ```typescript
398
- // settings.vue - Register new passkey
399
- async function registerPasskey() {
415
+ // settings.vue - Register new passkey (JWT-compatible)
416
+ const { registerPasskey, listPasskeys, deletePasskey } = useBetterAuth();
417
+
418
+ async function onRegisterPasskey() {
400
419
  try {
401
- // This calls generate-register-options verify-registration
402
- const result = await authClient.passkey.addPasskey({
403
- name: 'My Device', // Optional: passkey name
404
- });
420
+ // Use composable method which handles challengeId for JWT mode
421
+ const result = await registerPasskey('My Device'); // Optional name
405
422
 
406
- if (result.error) {
407
- showError(result.error.message);
423
+ if (!result.success) {
424
+ showError(result.error || 'Passkey registration failed');
408
425
  return;
409
426
  }
410
427
 
@@ -420,23 +437,32 @@ async function registerPasskey() {
420
437
  }
421
438
  }
422
439
 
423
- // List user's passkeys
440
+ // List user's passkeys (works in both modes)
424
441
  async function loadPasskeys() {
425
- const result = await authClient.passkey.listUserPasskeys();
426
- if (!result.error) {
427
- passkeys.value = result.data || [];
442
+ const result = await listPasskeys();
443
+ if (result.success) {
444
+ passkeys.value = result.passkeys || [];
428
445
  }
429
446
  }
430
447
 
431
- // Delete a passkey
432
- async function deletePasskey(passkeyId: string) {
433
- const result = await authClient.passkey.deletePasskey({ id: passkeyId });
434
- if (!result.error) {
448
+ // Delete a passkey (works in both modes)
449
+ async function onDeletePasskey(passkeyId: string) {
450
+ const result = await deletePasskey(passkeyId);
451
+ if (result.success) {
435
452
  await loadPasskeys();
436
453
  }
437
454
  }
438
455
  ```
439
456
 
457
+ **Alternative for Cookie mode only:**
458
+
459
+ If you're only using Cookie mode (`cookies: true`), you can use `authClient.passkey` directly:
460
+
461
+ ```typescript
462
+ // Cookie mode ONLY - does NOT work with JWT mode
463
+ const result = await authClient.passkey.addPasskey({ name: 'My Device' });
464
+ ```
465
+
440
466
  ---
441
467
 
442
468
  ## Better-Auth Hooks: Limitations & Warnings