@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.
- package/dist/config.env.js +16 -133
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +4 -0
- package/dist/core/modules/better-auth/better-auth.config.d.ts +3 -0
- package/dist/core/modules/better-auth/better-auth.config.js +176 -47
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.d.ts +5 -1
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +101 -8
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-challenge.service.d.ts +20 -0
- package/dist/core/modules/better-auth/core-better-auth-challenge.service.js +142 -0
- package/dist/core/modules/better-auth/core-better-auth-challenge.service.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +1 -1
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-web.helper.d.ts +2 -0
- package/dist/core/modules/better-auth/core-better-auth-web.helper.js +29 -1
- package/dist/core/modules/better-auth/core-better-auth-web.helper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js +5 -13
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.middleware.d.ts +0 -1
- package/dist/core/modules/better-auth/core-better-auth.middleware.js +6 -19
- package/dist/core/modules/better-auth/core-better-auth.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.module.d.ts +4 -1
- package/dist/core/modules/better-auth/core-better-auth.module.js +53 -19
- package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.resolver.js +7 -6
- package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.d.ts +0 -2
- package/dist/core/modules/better-auth/core-better-auth.service.js +23 -37
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core.module.js +3 -0
- package/dist/core.module.js.map +1 -1
- package/dist/server/modules/better-auth/better-auth.module.d.ts +4 -1
- package/dist/server/modules/better-auth/better-auth.module.js +4 -1
- package/dist/server/modules/better-auth/better-auth.module.js.map +1 -1
- package/dist/server/server.module.js +1 -4
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/config.env.ts +24 -174
- package/src/core/common/interfaces/server-options.interface.ts +288 -35
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +82 -56
- package/src/core/modules/better-auth/README.md +132 -35
- package/src/core/modules/better-auth/better-auth.config.ts +402 -70
- package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +158 -18
- package/src/core/modules/better-auth/core-better-auth-challenge.service.ts +254 -0
- package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +1 -1
- package/src/core/modules/better-auth/core-better-auth-web.helper.ts +64 -1
- package/src/core/modules/better-auth/core-better-auth.controller.ts +6 -14
- package/src/core/modules/better-auth/core-better-auth.middleware.ts +7 -20
- package/src/core/modules/better-auth/core-better-auth.module.ts +135 -25
- package/src/core/modules/better-auth/core-better-auth.resolver.ts +7 -6
- package/src/core/modules/better-auth/core-better-auth.service.ts +27 -48
- package/src/core.module.ts +5 -0
- package/src/server/modules/better-auth/better-auth.module.ts +40 -10
- 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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
* //
|
|
357
|
-
* google: {
|
|
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
|
-
*
|
|
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
|
|
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
|
-
* - `
|
|
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
|
-
* - `
|
|
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
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
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
|
-
*
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
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
|
-
* **
|
|
1306
|
-
* This
|
|
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
|
-
* - `
|
|
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
|
-
*
|
|
1522
|
+
*
|
|
1523
|
+
* @default undefined (enabled by default)
|
|
1313
1524
|
*
|
|
1314
1525
|
* @example
|
|
1315
1526
|
* ```typescript
|
|
1316
|
-
* // JWT
|
|
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
|
|
1382
|
-
*
|
|
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
|
-
* - `
|
|
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 }`:
|
|
1414
|
-
*
|
|
1665
|
+
* - `false` or `{ enabled: false }`: Explicitly disable
|
|
1666
|
+
*
|
|
1667
|
+
* @default undefined (enabled by default)
|
|
1415
1668
|
*
|
|
1416
1669
|
* @example
|
|
1417
1670
|
* ```typescript
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1420
|
-
* twoFactor: { appName: 'My App' }, //
|
|
1421
|
-
* twoFactor: false, //
|
|
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
|
-
####
|
|
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
|
-
//
|
|
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
|
-
|
|
142
|
-
|
|
148
|
+
};
|
|
149
|
+
```
|
|
143
150
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
####
|
|
164
|
+
#### Disabling Features:
|
|
154
165
|
```typescript
|
|
155
166
|
const config = {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
//
|
|
163
|
-
betterAuth:
|
|
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
|
|
355
|
-
const result = await
|
|
368
|
+
// Use composable method which handles challengeId for JWT mode
|
|
369
|
+
const result = await authenticateWithPasskey();
|
|
356
370
|
|
|
357
|
-
|
|
358
|
-
|
|
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.
|
|
364
|
-
setUser(result.
|
|
365
|
-
} else
|
|
366
|
-
//
|
|
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
|
-
|
|
415
|
+
// settings.vue - Register new passkey (JWT-compatible)
|
|
416
|
+
const { registerPasskey, listPasskeys, deletePasskey } = useBetterAuth();
|
|
417
|
+
|
|
418
|
+
async function onRegisterPasskey() {
|
|
400
419
|
try {
|
|
401
|
-
//
|
|
402
|
-
const result = await
|
|
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.
|
|
407
|
-
showError(result.error
|
|
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
|
|
426
|
-
if (
|
|
427
|
-
passkeys.value = result.
|
|
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
|
|
433
|
-
const result = await
|
|
434
|
-
if (
|
|
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
|