nuxt-auth-utils 0.5.33 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +91 -16
  2. package/dist/module.d.mts +3 -3
  3. package/dist/module.json +5 -2
  4. package/dist/module.mjs +28 -31
  5. package/dist/runtime/app/components/AuthState.d.vue.ts +20 -0
  6. package/dist/runtime/app/composables/session.js +3 -2
  7. package/dist/runtime/server/api/session.delete.d.ts +1 -1
  8. package/dist/runtime/server/api/session.delete.js +2 -2
  9. package/dist/runtime/server/api/session.get.d.ts +1 -1
  10. package/dist/runtime/server/api/session.get.js +2 -2
  11. package/dist/runtime/server/lib/atproto/bluesky.d.ts +3 -3
  12. package/dist/runtime/server/lib/atproto/bluesky.js +6 -6
  13. package/dist/runtime/server/lib/oauth/apple.d.ts +1 -1
  14. package/dist/runtime/server/lib/oauth/apple.js +3 -4
  15. package/dist/runtime/server/lib/oauth/atlassian.d.ts +1 -1
  16. package/dist/runtime/server/lib/oauth/atlassian.js +8 -8
  17. package/dist/runtime/server/lib/oauth/auth0.d.ts +1 -1
  18. package/dist/runtime/server/lib/oauth/auth0.js +4 -4
  19. package/dist/runtime/server/lib/oauth/authentik.d.ts +1 -1
  20. package/dist/runtime/server/lib/oauth/authentik.js +6 -6
  21. package/dist/runtime/server/lib/oauth/azureb2c.d.ts +1 -1
  22. package/dist/runtime/server/lib/oauth/azureb2c.js +5 -5
  23. package/dist/runtime/server/lib/oauth/battledotnet.d.ts +1 -1
  24. package/dist/runtime/server/lib/oauth/battledotnet.js +6 -6
  25. package/dist/runtime/server/lib/oauth/box.d.ts +1 -1
  26. package/dist/runtime/server/lib/oauth/box.js +5 -5
  27. package/dist/runtime/server/lib/oauth/cloudflare.d.ts +1 -1
  28. package/dist/runtime/server/lib/oauth/cloudflare.js +5 -5
  29. package/dist/runtime/server/lib/oauth/cognito.d.ts +1 -1
  30. package/dist/runtime/server/lib/oauth/cognito.js +4 -4
  31. package/dist/runtime/server/lib/oauth/discord.d.ts +1 -1
  32. package/dist/runtime/server/lib/oauth/discord.js +4 -4
  33. package/dist/runtime/server/lib/oauth/dropbox.d.ts +1 -1
  34. package/dist/runtime/server/lib/oauth/dropbox.js +5 -5
  35. package/dist/runtime/server/lib/oauth/facebook.d.ts +1 -1
  36. package/dist/runtime/server/lib/oauth/facebook.js +7 -7
  37. package/dist/runtime/server/lib/oauth/gitea.d.ts +1 -1
  38. package/dist/runtime/server/lib/oauth/gitea.js +5 -5
  39. package/dist/runtime/server/lib/oauth/github.d.ts +1 -1
  40. package/dist/runtime/server/lib/oauth/github.js +6 -6
  41. package/dist/runtime/server/lib/oauth/gitlab.d.ts +1 -1
  42. package/dist/runtime/server/lib/oauth/gitlab.js +5 -5
  43. package/dist/runtime/server/lib/oauth/google.d.ts +1 -1
  44. package/dist/runtime/server/lib/oauth/google.js +4 -4
  45. package/dist/runtime/server/lib/oauth/heroku.d.ts +1 -1
  46. package/dist/runtime/server/lib/oauth/heroku.js +5 -5
  47. package/dist/runtime/server/lib/oauth/hubspot.d.ts +1 -1
  48. package/dist/runtime/server/lib/oauth/hubspot.js +4 -4
  49. package/dist/runtime/server/lib/oauth/instagram.d.ts +1 -1
  50. package/dist/runtime/server/lib/oauth/instagram.js +6 -6
  51. package/dist/runtime/server/lib/oauth/keycloak.d.ts +1 -1
  52. package/dist/runtime/server/lib/oauth/keycloak.js +6 -6
  53. package/dist/runtime/server/lib/oauth/kick.d.ts +1 -1
  54. package/dist/runtime/server/lib/oauth/kick.js +5 -5
  55. package/dist/runtime/server/lib/oauth/line.d.ts +1 -1
  56. package/dist/runtime/server/lib/oauth/line.js +6 -6
  57. package/dist/runtime/server/lib/oauth/linear.d.ts +1 -1
  58. package/dist/runtime/server/lib/oauth/linear.js +6 -6
  59. package/dist/runtime/server/lib/oauth/linkedin.d.ts +1 -1
  60. package/dist/runtime/server/lib/oauth/linkedin.js +4 -4
  61. package/dist/runtime/server/lib/oauth/livechat.d.ts +1 -1
  62. package/dist/runtime/server/lib/oauth/livechat.js +4 -4
  63. package/dist/runtime/server/lib/oauth/mastodon.d.ts +1 -1
  64. package/dist/runtime/server/lib/oauth/mastodon.js +8 -9
  65. package/dist/runtime/server/lib/oauth/microsoft.d.ts +1 -1
  66. package/dist/runtime/server/lib/oauth/microsoft.js +5 -5
  67. package/dist/runtime/server/lib/oauth/oidc.d.ts +1 -1
  68. package/dist/runtime/server/lib/oauth/oidc.js +6 -6
  69. package/dist/runtime/server/lib/oauth/okta.d.ts +4 -4
  70. package/dist/runtime/server/lib/oauth/okta.js +9 -9
  71. package/dist/runtime/server/lib/oauth/ory.d.ts +1 -1
  72. package/dist/runtime/server/lib/oauth/ory.js +6 -6
  73. package/dist/runtime/server/lib/oauth/osu.d.ts +1 -1
  74. package/dist/runtime/server/lib/oauth/osu.js +4 -4
  75. package/dist/runtime/server/lib/oauth/paypal.d.ts +1 -1
  76. package/dist/runtime/server/lib/oauth/paypal.js +5 -5
  77. package/dist/runtime/server/lib/oauth/polar.d.ts +1 -1
  78. package/dist/runtime/server/lib/oauth/polar.js +5 -5
  79. package/dist/runtime/server/lib/oauth/riotgames.d.ts +1 -1
  80. package/dist/runtime/server/lib/oauth/riotgames.js +5 -5
  81. package/dist/runtime/server/lib/oauth/roblox.d.ts +1 -1
  82. package/dist/runtime/server/lib/oauth/roblox.js +4 -4
  83. package/dist/runtime/server/lib/oauth/salesforce.d.ts +1 -1
  84. package/dist/runtime/server/lib/oauth/salesforce.js +5 -5
  85. package/dist/runtime/server/lib/oauth/seznam.d.ts +1 -1
  86. package/dist/runtime/server/lib/oauth/seznam.js +4 -4
  87. package/dist/runtime/server/lib/oauth/shopifyCustomer.d.ts +1 -1
  88. package/dist/runtime/server/lib/oauth/shopifyCustomer.js +7 -7
  89. package/dist/runtime/server/lib/oauth/slack.d.ts +1 -1
  90. package/dist/runtime/server/lib/oauth/slack.js +5 -5
  91. package/dist/runtime/server/lib/oauth/spotify.d.ts +49 -2
  92. package/dist/runtime/server/lib/oauth/spotify.js +4 -4
  93. package/dist/runtime/server/lib/oauth/steam.d.ts +1 -1
  94. package/dist/runtime/server/lib/oauth/steam.js +7 -7
  95. package/dist/runtime/server/lib/oauth/strava.d.ts +1 -1
  96. package/dist/runtime/server/lib/oauth/strava.js +5 -5
  97. package/dist/runtime/server/lib/oauth/tiktok.d.ts +1 -1
  98. package/dist/runtime/server/lib/oauth/tiktok.js +5 -5
  99. package/dist/runtime/server/lib/oauth/twitch.d.ts +1 -1
  100. package/dist/runtime/server/lib/oauth/twitch.js +5 -5
  101. package/dist/runtime/server/lib/oauth/vk.d.ts +1 -1
  102. package/dist/runtime/server/lib/oauth/vk.js +5 -5
  103. package/dist/runtime/server/lib/oauth/wechat.d.ts +1 -1
  104. package/dist/runtime/server/lib/oauth/wechat.js +7 -7
  105. package/dist/runtime/server/lib/oauth/workos.d.ts +1 -1
  106. package/dist/runtime/server/lib/oauth/workos.js +3 -4
  107. package/dist/runtime/server/lib/oauth/x.d.ts +1 -1
  108. package/dist/runtime/server/lib/oauth/x.js +4 -4
  109. package/dist/runtime/server/lib/oauth/xsuaa.d.ts +1 -1
  110. package/dist/runtime/server/lib/oauth/xsuaa.js +4 -4
  111. package/dist/runtime/server/lib/oauth/yandex.d.ts +1 -1
  112. package/dist/runtime/server/lib/oauth/yandex.js +4 -4
  113. package/dist/runtime/server/lib/oauth/zendesk.d.ts +110 -0
  114. package/dist/runtime/server/lib/oauth/zendesk.js +109 -0
  115. package/dist/runtime/server/lib/oauth/zitadel.d.ts +1 -1
  116. package/dist/runtime/server/lib/oauth/zitadel.js +6 -6
  117. package/dist/runtime/server/lib/utils.d.ts +8 -8
  118. package/dist/runtime/server/lib/utils.js +5 -7
  119. package/dist/runtime/server/lib/webauthn/authenticate.d.ts +1 -1
  120. package/dist/runtime/server/lib/webauthn/authenticate.js +7 -8
  121. package/dist/runtime/server/lib/webauthn/register.d.ts +2 -2
  122. package/dist/runtime/server/lib/webauthn/register.js +9 -10
  123. package/dist/runtime/server/plugins/oauth-hash.d.ts +2 -0
  124. package/dist/runtime/server/plugins/oauth-hash.js +8 -0
  125. package/dist/runtime/server/routes/atproto/client-metadata.json.get.d.ts +1 -1
  126. package/dist/runtime/server/routes/atproto/client-metadata.json.get.js +5 -5
  127. package/dist/runtime/server/utils/atproto.d.ts +2 -2
  128. package/dist/runtime/server/utils/atproto.js +2 -3
  129. package/dist/runtime/server/utils/password.js +1 -1
  130. package/dist/runtime/server/utils/session.d.ts +29 -16
  131. package/dist/runtime/server/utils/session.js +72 -29
  132. package/dist/runtime/types/event.d.ts +27 -3
  133. package/dist/runtime/types/index.d.ts +1 -1
  134. package/dist/runtime/types/oauth-config.d.ts +4 -4
  135. package/dist/runtime/types/webauthn.d.ts +14 -15
  136. package/package.json +12 -9
  137. package/dist/runtime/server/plugins/oauth.d.ts +0 -2
  138. package/dist/runtime/server/plugins/oauth.js +0 -16
  139. package/dist/runtime/server/plugins/ws.d.ts +0 -2
  140. package/dist/runtime/server/plugins/ws.js +0 -7
package/README.md CHANGED
@@ -29,6 +29,8 @@ It has few dependencies (only from [UnJS](https://github.com/unjs)), run on mult
29
29
 
30
30
  ## Requirements
31
31
 
32
+ This module requires Nuxt 4.6 or later. If you upgrade from v0.5, read [Migrate from v0.5](#migrate-from-v05).
33
+
32
34
  This module only works with a Nuxt server running as it uses server API routes (`nuxt build`).
33
35
 
34
36
  This means that you cannot use this module with `nuxt generate`.
@@ -43,14 +45,19 @@ You can anyway use [Hybrid Rendering](#hybrid-rendering) to pre-render pages of
43
45
  npx nuxi@latest module add auth-utils
44
46
  ```
45
47
 
46
- 2. Add a `NUXT_SESSION_PASSWORD` env variable with at least 32 characters in the `.env`.
48
+ 2. Set the `NUXT_APP_SECRET` env variable in production, with at least 32 characters.
49
+
50
+ ```bash
51
+ # Generate a secret
52
+ openssl rand -base64 32
53
+ ```
47
54
 
48
55
  ```bash
49
56
  # .env
50
- NUXT_SESSION_PASSWORD=password-with-at-least-32-characters
57
+ NUXT_APP_SECRET=<the-generated-secret>
51
58
  ```
52
59
 
53
- Nuxt Auth Utils generates one for you when running Nuxt in development the first time if no `NUXT_SESSION_PASSWORD` is set.
60
+ Nuxt Auth Utils seals the session cookie with a secret that it derives from the [Nuxt `appSecret`](https://nuxt.com/blog/v4-6). In development, Nuxt generates an `appSecret` for you if none is set. Builds never generate one, so you must set `NUXT_APP_SECRET` in production.
54
61
 
55
62
  3. That's it! You can now add authentication to your Nuxt app ✨
56
63
 
@@ -122,6 +129,19 @@ interface UserSessionComposable {
122
129
 
123
130
  The following helpers are auto-imported in your `server/` directory.
124
131
 
132
+ They accept the event of a handler defined with `defineEventHandler` from `nuxt/server`. On Nuxt 4, they also accept the event of a handler defined with the auto-imported `defineEventHandler` from h3.
133
+
134
+ ```ts
135
+ // server/api/me.get.ts
136
+ import { defineEventHandler } from 'nuxt/server'
137
+
138
+ export default defineEventHandler(async (event) => {
139
+ const { user } = await requireUserSession(event)
140
+
141
+ return user
142
+ })
143
+ ```
144
+
125
145
  ### Session Management
126
146
 
127
147
  ```ts
@@ -152,6 +172,9 @@ await clearUserSession(event)
152
172
 
153
173
  // Require a user session (send back 401 if no `user` key in session)
154
174
  const session = await requireUserSession(event)
175
+
176
+ // Customize the error status and message
177
+ const session = await requireUserSession(event, { status: 403, message: 'Forbidden' })
155
178
  ```
156
179
 
157
180
  You can define the type for your user session by creating a type declaration file (for example, `auth.d.ts`) in your project to augment the `UserSession` type:
@@ -261,6 +284,7 @@ It can also be set using environment variables:
261
284
  - X (Twitter)
262
285
  - XSUAA
263
286
  - Yandex
287
+ - Zendesk
264
288
  - Zitadel
265
289
 
266
290
  You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](https://github.com/atinux/nuxt-auth-utils/tree/main/src/runtime/server/lib/oauth).
@@ -270,6 +294,8 @@ You can add your favorite provider by creating a new file in [src/runtime/server
270
294
  Example: `~/server/routes/auth/github.get.ts`
271
295
 
272
296
  ```ts
297
+ import { sendRedirect } from 'nuxt/server'
298
+
273
299
  export default defineOAuthGitHubEventHandler({
274
300
  config: {
275
301
  emailRequired: true
@@ -290,6 +316,8 @@ export default defineOAuthGitHubEventHandler({
290
316
  })
291
317
  ```
292
318
 
319
+ The `event` given to `onSuccess` and `onError` is a `RequestEvent` from `nuxt/server`. Import the helpers you use from `nuxt/server`, for example `sendRedirect`. On Nuxt 4, the auto-imported h3 helpers also work, but TypeScript reports a type error.
320
+
293
321
  Make sure to set the callback URL in your OAuth app settings as `<your-domain>/auth/github`.
294
322
 
295
323
  If the redirect URL mismatch in production, this means that the module cannot guess the right redirect URL. You can set the `NUXT_OAUTH_<PROVIDER>_REDIRECT_URL` env variable to overwrite the default one.
@@ -391,7 +419,7 @@ To enable WebAuthn you need to:
391
419
  1. Install the peer dependencies:
392
420
 
393
421
  ```bash
394
- npx nypm i @simplewebauthn/server@11 @simplewebauthn/browser@11
422
+ npx nypm i @simplewebauthn/server@13 @simplewebauthn/browser@13
395
423
  ```
396
424
 
397
425
  2. Enable it in your `nuxt.config.ts`
@@ -449,7 +477,7 @@ export default defineWebAuthnRegisterEventHandler({
449
477
  // And verify that the email is the same as the one in session
450
478
  const session = await getUserSession(event)
451
479
  if (session.user?.email && session.user.email !== userBody.userName) {
452
- throw createError({ statusCode: 400, message: 'Email not matching current session' })
480
+ throw createError({ status: 400, message: 'Email not matching current session' })
453
481
  }
454
482
 
455
483
  // If he registers a new account with credentials
@@ -494,7 +522,7 @@ export default defineWebAuthnAuthenticateEventHandler({
494
522
  const credentials = await useDatabase().sql`...`
495
523
  // If no credentials are found, the authentication cannot be completed
496
524
  if (!credentials.length)
497
- throw createError({ statusCode: 400, message: 'User not found' })
525
+ throw createError({ status: 400, message: 'User not found' })
498
526
 
499
527
  // If user is found, only allow credentials that are registered
500
528
  // The browser will automatically try to use the credential that it knows about
@@ -508,7 +536,7 @@ export default defineWebAuthnAuthenticateEventHandler({
508
536
 
509
537
  // If the credential is not found, there is no account to log in to
510
538
  if (!credential)
511
- throw createError({ statusCode: 400, message: 'Credential not found' })
539
+ throw createError({ status: 400, message: 'Credential not found' })
512
540
 
513
541
  return credential
514
542
  },
@@ -548,7 +576,7 @@ export default defineWebAuthnAuthenticateEventHandler({
548
576
  > await useStorage().removeItem(`attempt:${attemptId}`)
549
577
  >
550
578
  > if (!challenge)
551
- > throw createError({ statusCode: 400, message: 'Challenge expired' })
579
+ > throw createError({ status: 400, message: 'Challenge expired' })
552
580
  >
553
581
  > return challenge
554
582
  > },
@@ -606,7 +634,8 @@ We leverage hooks to let you extend the session data with your own data or log w
606
634
 
607
635
  ```ts
608
636
  // server/plugins/session.ts
609
- export default defineNitroPlugin(() => {
637
+ // A server plugin is a function, so this file runs on Nuxt 4 (Nitro 2) and Nuxt 5 (Nitro 3)
638
+ export default () => {
610
639
  // Called when the session is fetched during SSR for the Vue composable (/api/_auth/session)
611
640
  // Or when we call useUserSession().fetch()
612
641
  sessionHooks.hook('fetch', async (session, event) => {
@@ -619,7 +648,7 @@ export default defineNitroPlugin(() => {
619
648
  sessionHooks.hook('clear', async (session, event) => {
620
649
  // Log that user logged out
621
650
  })
622
- })
651
+ }
623
652
  ```
624
653
 
625
654
  ## Server-Side Rendering
@@ -727,10 +756,13 @@ export default defineNuxtConfig({
727
756
  })
728
757
  ```
729
758
 
730
- You can use the `requireUserSession` function in the `upgrade` function to check if the user is authenticated before upgrading the WebSocket connection.
759
+ You can use the `requireUserSession` function in the `upgrade` function to check if the user is authenticated before upgrading the WebSocket connection. In WebSocket handlers, the session is read-only: `setUserSession`, `replaceUserSession` and `clearUserSession` throw an error when given a WebSocket peer or upgrade request.
731
760
 
732
761
  ```ts
733
762
  // server/routes/ws.ts
763
+ // `defineWebSocketHandler` is not auto-imported on Nuxt 5, `h3` works on Nuxt 4 and Nuxt 5
764
+ import { defineWebSocketHandler } from 'h3'
765
+
734
766
  export default defineWebSocketHandler({
735
767
  async upgrade(request) {
736
768
  // Make sure the user is authenticated before upgrading the WebSocket connection
@@ -772,7 +804,7 @@ onMounted(open)
772
804
 
773
805
  ## Configuration
774
806
 
775
- We leverage `runtimeConfig.session` to give the defaults option to [h3 `useSession`](https://h3.unjs.io/examples/handle-session).
807
+ Nuxt Auth Utils stores the user session with `useSession` from `nuxt/server`. It uses `runtimeConfig.session` for the default options.
776
808
 
777
809
  You can overwrite the options in your `nuxt.config.ts`:
778
810
 
@@ -791,14 +823,21 @@ Our defaults are:
791
823
 
792
824
  ```ts
793
825
  {
794
- name: 'nuxt-session',
795
- password: process.env.NUXT_SESSION_PASSWORD || '',
826
+ name: 'nuxt-auth-session',
796
827
  cookie: {
797
828
  sameSite: 'lax'
798
829
  }
799
830
  }
800
831
  ```
801
832
 
833
+ The available options are:
834
+
835
+ - `name`: the name of the session cookie.
836
+ - `maxAge`: the lifetime of the cookie and of the sealed session, in seconds.
837
+ - `cookie`: the cookie options, merged over `httpOnly: true`, `secure: true`, `sameSite: 'lax'`, and `path: '/'`.
838
+
839
+ The session is sealed with a secret that Nuxt derives from `appSecret`. The module ignores `runtimeConfig.session.password`.
840
+
802
841
  You can also overwrite the session config by passing it as 3rd argument of the `setUserSession` and `replaceUserSession` functions:
803
842
 
804
843
  ```ts
@@ -807,11 +846,41 @@ await setUserSession(event, { ... } , {
807
846
  })
808
847
  ```
809
848
 
810
- Checkout the [`SessionConfig`](https://github.com/unjs/h3/blob/c04c458810e34eb15c1647e1369e7d7ef19f567d/src/utils/session.ts#L20) for all options.
849
+ ## Migrate from v0.5
850
+
851
+ Nuxt Auth Utils v0.6 moves to `nuxt/server`, the server API of Nuxt 4.6. The same server code runs on Nuxt 4 and Nuxt 5. This release has breaking changes.
852
+
853
+ > [!WARNING]
854
+ > All users are logged out once after the upgrade. The session cookie has a new name and a new format.
855
+
856
+ To migrate your app, follow these steps:
857
+
858
+ 1. Upgrade Nuxt to 4.6 or later.
859
+ 2. Generate a secret with `openssl rand -base64 32`.
860
+ 3. Set the secret as `NUXT_APP_SECRET` in your production environment.
861
+ 4. On Cloudflare Workers, set `compatibility_date` to `2025-04-01` or later, or add the `nodejs_compat_populate_process_env` compatibility flag. Otherwise the module can't read `NUXT_APP_SECRET` and the `NUXT_OAUTH_*` variables from your Worker variables and secrets.
862
+ 5. Remove `NUXT_SESSION_PASSWORD` from your environment and from your `.env` file. The module logs a warning while it is set.
863
+ 6. Change `statusCode` to `status` in `createError()` calls.
864
+ 7. In `onSuccess` and `onError` callbacks, import helpers such as `sendRedirect` from `nuxt/server`.
865
+
866
+ The other changes in v0.6 are:
867
+
868
+ - The session cookie is `nuxt-auth-session`. It was `nuxt-session`, which is now the default name of `useSession` from `nuxt/server`.
869
+ - The session can't be read from the `x-nuxt-session-session` request header anymore.
870
+ - The `sessionHeader`, `seal`, `crypto`, `generateId`, and `password` session options are removed.
871
+ - The `event` in the OAuth and WebAuthn callbacks is a `RequestEvent` from `nuxt/server`, and `error` is a `NuxtError`.
872
+ - In WebSocket handlers, `setUserSession`, `replaceUserSession` and `clearUserSession` throw an error, as the session cookie can't be sent back to the client.
873
+ - `requireUserSession` accepts a `status` option. The `statusCode` option still works, but it's deprecated.
874
+ - Nuxt throws a 500 error if a session cookie is larger than 4096 bytes.
875
+ - The session `maxAge` counts from the last write: each `setUserSession` call extends the cookie. In v0.5, it counted from when the session was created.
876
+ - The session can't be named `nuxt-session`, the cookie of `useSession()` from `nuxt/server`. The module warns and uses `nuxt-auth-session` instead.
877
+ - WebAuthn requires SimpleWebAuthn v13, with `@simplewebauthn/server` 13.3.2 or later. Import its JSON types from `@simplewebauthn/server` or `@simplewebauthn/browser` instead of `@simplewebauthn/types`.
811
878
 
812
879
  ## Shared session between microservices and subdomains
813
880
 
814
- To share a session between multiple applications, use the same `NUXT_SESSION_PASSWORD` and session `name` in each of them.
881
+ To share a session between multiple applications, use the same `NUXT_APP_SECRET` and session `name` in each of them.
882
+
883
+ Any application with the same `NUXT_APP_SECRET` can also derive the other keys Nuxt derives from it, so share it only between applications you trust equally.
815
884
 
816
885
  If the applications run on different subdomains, also set the session cookie `domain` to the parent domain in each `nuxt.config.ts`:
817
886
 
@@ -846,6 +915,12 @@ pnpm run dev:prepare
846
915
  # Develop with the playground
847
916
  pnpm run dev
848
917
 
918
+ # Develop with the playground on the Nuxt 5 nightly (Nitro 3), run `pnpm install` to go back to the lockfile
919
+ pnpm run dev:v5
920
+
921
+ # Run the tests on the Nuxt 5 nightly
922
+ pnpm run install:nuxt5 && pnpm run dev:prepare && pnpm run test
923
+
849
924
  # Build the playground
850
925
  pnpm run dev:build
851
926
 
package/dist/module.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ScryptConfig } from '@adonisjs/hash/types';
2
- import { SessionConfig } from 'h3';
2
+ import { SessionConfig } from 'nuxt/server';
3
3
 
4
4
  interface ModuleOptions {
5
5
  /**
@@ -33,9 +33,9 @@ declare module 'nuxt/schema' {
33
33
  scrypt: ScryptConfig;
34
34
  };
35
35
  /**
36
- * Session configuration
36
+ * Session configuration, the session is sealed with a secret derived from `appSecret` (`NUXT_APP_SECRET`)
37
37
  */
38
- session: SessionConfig;
38
+ session: Omit<SessionConfig, 'password'>;
39
39
  }
40
40
  interface PublicRuntimeConfig {
41
41
  auth: {
package/dist/module.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.5.33",
4
+ "compatibility": {
5
+ "nuxt": ">=4.6.0"
6
+ },
7
+ "version": "0.6.0",
5
8
  "builder": {
6
- "@nuxt/module-builder": "1.0.2",
9
+ "@nuxt/module-builder": "1.0.3",
7
10
  "unbuild": "3.6.1"
8
11
  }
9
12
  }
package/dist/module.mjs CHANGED
@@ -1,14 +1,14 @@
1
- import { defineNuxtModule, createResolver, addComponentsDir, addImports, addPlugin, addServerPlugin, addServerImportsDir, logger, addServerHandler } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addComponentsDir, addImports, addPlugin, addServerPlugin, addServerImportsDir, logger, addServerHandler, getNitroVersion } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
- import { readFile, writeFile } from 'node:fs/promises';
4
- import { join } from 'pathe';
5
- import { randomUUID } from 'uncrypto';
6
3
  import { atprotoProviders, atprotoProviderDefaultClientMetadata, getClientMetadataFilename } from '../dist/runtime/utils/atproto.js';
7
4
 
8
5
  const module$1 = defineNuxtModule({
9
6
  meta: {
10
7
  name: "auth-utils",
11
- configKey: "auth"
8
+ configKey: "auth",
9
+ compatibility: {
10
+ nuxt: ">=4.6.0"
11
+ }
12
12
  },
13
13
  // Default configuration options of the Nuxt module
14
14
  defaults: {
@@ -36,11 +36,8 @@ const module$1 = defineNuxtModule({
36
36
  addPlugin(resolver.resolve("./runtime/app/plugins/session.server"));
37
37
  addPlugin(resolver.resolve("./runtime/app/plugins/session.client"));
38
38
  }
39
- addServerPlugin(resolver.resolve("./runtime/server/plugins/oauth"));
39
+ addServerPlugin(resolver.resolve("./runtime/server/plugins/oauth-hash"));
40
40
  addServerImportsDir(resolver.resolve("./runtime/server/lib/oauth"));
41
- if (nuxt.options.nitro?.experimental?.websocket) {
42
- addServerPlugin(resolver.resolve("./runtime/server/plugins/ws"));
43
- }
44
41
  if (options.webAuthn) {
45
42
  const missingDeps = [];
46
43
  const peerDeps = ["@simplewebauthn/server", "@simplewebauthn/browser"];
@@ -68,16 +65,22 @@ const module$1 = defineNuxtModule({
68
65
  route: "/api/_auth/session",
69
66
  method: "get"
70
67
  });
71
- nuxt.options.nitro.unenv ||= {};
72
- nuxt.options.nitro.unenv.external ||= [];
73
- if (!nuxt.options.nitro.unenv.external.includes("node:crypto")) {
74
- nuxt.options.nitro.unenv.external.push("node:crypto");
68
+ if (getNitroVersion(nuxt) === 2) {
69
+ const nitroOptions = nuxt.options.nitro;
70
+ nitroOptions.unenv ||= {};
71
+ nitroOptions.unenv.external ||= [];
72
+ if (!nitroOptions.unenv.external.includes("node:crypto")) {
73
+ nitroOptions.unenv.external.push("node:crypto");
74
+ }
75
75
  }
76
76
  const runtimeConfig = nuxt.options.runtimeConfig;
77
77
  const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
78
+ if (process.env[envSessionPassword] || runtimeConfig.session?.password) {
79
+ logger.withTag("nuxt-auth-utils").warn(`\`${envSessionPassword}\` (\`runtimeConfig.session.password\`) is not used anymore. Set \`NUXT_APP_SECRET\` instead, for example with \`openssl rand -base64 32\`. Sessions are now sealed with a secret derived from \`appSecret\`, so users are logged out once.`);
80
+ }
78
81
  runtimeConfig.session = defu(runtimeConfig.session, {
79
- name: "nuxt-session",
80
- password: "",
82
+ // Declared so `NUXT_SESSION_NAME` can override it at runtime
83
+ name: "nuxt-auth-session",
81
84
  cookie: {
82
85
  sameSite: "lax"
83
86
  }
@@ -85,22 +88,6 @@ const module$1 = defineNuxtModule({
85
88
  runtimeConfig.hash = defu(runtimeConfig.hash, {
86
89
  scrypt: options.hash?.scrypt
87
90
  });
88
- if (nuxt.options.dev && !process.env[envSessionPassword]) {
89
- if (nuxt.options.runtimeConfig.session.password) {
90
- process.env[envSessionPassword] = nuxt.options.runtimeConfig.session.password;
91
- } else {
92
- const password = process.env[envSessionPassword] = randomUUID().replace(/-/g, "");
93
- const envPath = join(nuxt.options.rootDir, ".env");
94
- const envContent = await readFile(envPath, "utf-8").catch(() => "");
95
- if (!envContent.includes(envSessionPassword)) {
96
- await writeFile(
97
- envPath,
98
- `${envContent ? envContent + "\n" : envContent}${envSessionPassword}=${password}`,
99
- "utf-8"
100
- );
101
- }
102
- }
103
- }
104
91
  runtimeConfig.public.auth = defu(runtimeConfig.public.auth, {
105
92
  loadStrategy: options.loadStrategy ?? "server-first"
106
93
  });
@@ -436,6 +423,16 @@ const module$1 = defineNuxtModule({
436
423
  redirectURL: "",
437
424
  scope: []
438
425
  });
426
+ runtimeConfig.oauth.zendesk = defu(runtimeConfig.oauth.zendesk, {
427
+ clientId: "",
428
+ clientSecret: "",
429
+ subdomain: "",
430
+ redirectURL: "",
431
+ marketplaceName: "",
432
+ marketplaceOrgId: "",
433
+ marketplaceBotId: "",
434
+ marketplaceAppId: ""
435
+ });
439
436
  }
440
437
  });
441
438
 
@@ -0,0 +1,20 @@
1
+ declare var __VLS_1: {
2
+ loggedIn: any;
3
+ user: any;
4
+ session: any;
5
+ clear: any;
6
+ }, __VLS_3: {};
7
+ type __VLS_Slots = {} & {
8
+ default?: (props: typeof __VLS_1) => any;
9
+ } & {
10
+ placeholder?: (props: typeof __VLS_3) => any;
11
+ };
12
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
13
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
16
+ type __VLS_WithSlots<T, S> = T & {
17
+ new (): {
18
+ $slots: S;
19
+ };
20
+ };
@@ -1,4 +1,3 @@
1
- import { appendResponseHeader } from "h3";
2
1
  import { useState, computed, useRequestFetch, useRequestEvent } from "#imports";
3
2
  export function useUserSession() {
4
3
  const serverEvent = import.meta.server ? useRequestEvent() : null;
@@ -10,8 +9,10 @@ export function useUserSession() {
10
9
  method: "DELETE",
11
10
  onResponse({ response: { headers } }) {
12
11
  if (import.meta.server && serverEvent) {
12
+ const res = serverEvent.res;
13
13
  for (const setCookie of headers.getSetCookie()) {
14
- appendResponseHeader(serverEvent, "Set-Cookie", setCookie);
14
+ if (res.headers) res.headers.append("set-cookie", setCookie);
15
+ else res.appendHeader?.("set-cookie", setCookie);
15
16
  }
16
17
  }
17
18
  }
@@ -1,4 +1,4 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
1
+ declare const _default: import("nuxt/server").EventHandler<Promise<{
2
2
  loggedOut: boolean;
3
3
  }>>;
4
4
  export default _default;
@@ -1,6 +1,6 @@
1
- import { eventHandler } from "h3";
1
+ import { defineEventHandler } from "nuxt/server";
2
2
  import { clearUserSession } from "../utils/session.js";
3
- export default eventHandler(async (event) => {
3
+ export default defineEventHandler(async (event) => {
4
4
  await clearUserSession(event);
5
5
  return { loggedOut: true };
6
6
  });
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<UserSession>>;
1
+ declare const _default: import("nuxt/server").EventHandler<Promise<UserSession>>;
2
2
  export default _default;
@@ -1,6 +1,6 @@
1
- import { eventHandler } from "h3";
1
+ import { defineEventHandler } from "nuxt/server";
2
2
  import { getUserSession, sessionHooks } from "../utils/session.js";
3
- export default eventHandler(async (event) => {
3
+ export default defineEventHandler(async (event) => {
4
4
  const session = await getUserSession(event);
5
5
  if (Object.keys(session).length > 0) {
6
6
  await sessionHooks.callHookParallel("fetch", session, event);
@@ -1,4 +1,4 @@
1
- import type { H3Event } from 'h3';
1
+ import type { RequestEvent } from 'nuxt/server';
2
2
  import type { NodeSavedSession, NodeSavedSessionStore, NodeSavedState, NodeSavedStateStore } from '@atproto/oauth-client-node';
3
3
  import type { AppBskyActorDefs } from '@atproto/api';
4
4
  import type { OAuthConfig } from '#auth-utils';
@@ -22,11 +22,11 @@ type BlueSkyTokens = NodeSavedSession['tokenSet'];
22
22
  export declare function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBlueskyConfig, {
23
23
  user: BlueSkyUser;
24
24
  tokens: BlueSkyTokens;
25
- }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
25
+ }>): import("nuxt/server").EventHandler<Promise<any>>;
26
26
  export declare class StateStore implements NodeSavedStateStore {
27
27
  private event;
28
28
  private readonly stateKey;
29
- constructor(event: H3Event);
29
+ constructor(event: RequestEvent);
30
30
  get(): Promise<NodeSavedState | undefined>;
31
31
  set(key: string, val: NodeSavedState): Promise<void>;
32
32
  del(): Promise<void>;
@@ -1,9 +1,9 @@
1
- import { createError, eventHandler, getQuery, sendRedirect, getCookie, setCookie, deleteCookie } from "h3";
1
+ import { defineEventHandler, createError, deleteCookie, getCookie, getQuery, sendRedirect, setCookie } from "nuxt/server";
2
2
  import { NodeOAuthClient, OAuthCallbackError, OAuthResolverError, OAuthResponseError } from "@atproto/oauth-client-node";
3
3
  import { Agent } from "@atproto/api";
4
4
  import { getAtprotoClientMetadata } from "../../utils/atproto.js";
5
5
  export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
6
- return eventHandler(async (event) => {
6
+ return defineEventHandler(async (event) => {
7
7
  const clientMetadata = getAtprotoClientMetadata(event, "bluesky", config);
8
8
  const scopes = clientMetadata.scope?.split(" ") ?? [];
9
9
  const sessionStore = new SessionStore();
@@ -18,7 +18,7 @@ export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
18
18
  try {
19
19
  const handle = query.handle?.toString();
20
20
  if (!handle) throw createError({
21
- statusCode: 400,
21
+ status: 400,
22
22
  message: "Query parameter `handle` empty or missing. Please provide a valid Bluesky handle."
23
23
  });
24
24
  const url = await client.authorize(handle, { scope: clientMetadata.scope });
@@ -28,13 +28,13 @@ export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
28
28
  switch (true) {
29
29
  case err instanceof OAuthResponseError:
30
30
  return createError({
31
- statusCode: 500,
31
+ status: 500,
32
32
  message: `Bluesky login failed: ${err.errorDescription || "Unknown error"}`,
33
33
  data: err.payload
34
34
  });
35
35
  case err instanceof OAuthResolverError:
36
36
  return createError({
37
- statusCode: 400,
37
+ status: 400,
38
38
  message: `Bluesky login failed: ${err.message || "Unknown error"}`
39
39
  });
40
40
  default:
@@ -57,7 +57,7 @@ export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
57
57
  } catch (err) {
58
58
  if (!(err instanceof OAuthCallbackError)) throw err;
59
59
  const error = createError({
60
- statusCode: 500,
60
+ status: 500,
61
61
  message: `Bluesky login failed: ${err.message || "Unknown error"}`
62
62
  });
63
63
  if (!onError) throw error;
@@ -74,4 +74,4 @@ export declare function defineOAuthAppleEventHandler({ config, onSuccess, onErro
74
74
  user: OAuthAppleUser;
75
75
  payload: OAuthAppleTokens;
76
76
  tokens: any;
77
- }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
77
+ }>): import("nuxt/server").EventHandler<Promise<any>>;
@@ -1,16 +1,15 @@
1
- import { eventHandler, getRequestHeader, readBody, sendRedirect } from "h3";
1
+ import { defineEventHandler, getRequestHeader, readBody, sendRedirect, useRuntimeConfig } from "nuxt/server";
2
2
  import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
4
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, handleInvalidState, handleState, requestAccessToken, signJwt, verifyJwt } from "../utils.js";
5
- import { useRuntimeConfig } from "#imports";
6
5
  export function defineOAuthAppleEventHandler({
7
6
  config,
8
7
  onSuccess,
9
8
  onError
10
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
10
  }) {
12
- return eventHandler(async (event) => {
13
- config = defu(config, useRuntimeConfig(event).oauth?.apple, {
11
+ return defineEventHandler(async (event) => {
12
+ config = defu(config, useRuntimeConfig().oauth?.apple, {
14
13
  authorizationURL: config?.authorizationURL || "https://appleid.apple.com/auth/authorize",
15
14
  authorizationParams: {}
16
15
  });
@@ -91,5 +91,5 @@ export interface OAuthAtlassianConfig {
91
91
  export declare function defineOAuthAtlassianEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthAtlassianConfig, {
92
92
  user: AtlassianUser;
93
93
  tokens: AtlassianTokens;
94
- }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
94
+ }>): import("nuxt/server").EventHandler<Promise<any>>;
95
95
  export {};
@@ -1,14 +1,14 @@
1
- import { eventHandler, getQuery, sendRedirect } from "h3";
1
+ import { defineEventHandler, createError, getQuery, sendRedirect, useRuntimeConfig } from "nuxt/server";
2
+ import { $fetch } from "ofetch";
2
3
  import { withQuery } from "ufo";
3
4
  import { defu } from "defu";
4
5
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, handleInvalidState, handleState, requestAccessToken } from "../utils.js";
5
- import { useRuntimeConfig, createError } from "#imports";
6
6
  export function defineOAuthAtlassianEventHandler({
7
7
  config,
8
8
  onSuccess,
9
9
  onError
10
10
  }) {
11
- return eventHandler(async (event) => {
11
+ return defineEventHandler(async (event) => {
12
12
  config = defu(config, useRuntimeConfig().oauth?.atlassian, {
13
13
  authorizationURL: "https://auth.atlassian.com/authorize",
14
14
  tokenURL: "https://auth.atlassian.com/oauth/token",
@@ -45,7 +45,7 @@ export function defineOAuthAtlassianEventHandler({
45
45
  }
46
46
  if (query.error) {
47
47
  const error = createError({
48
- statusCode: 401,
48
+ status: 401,
49
49
  message: `Atlassian login failed: ${query.error || "Unknown error"}`,
50
50
  data: query
51
51
  });
@@ -78,8 +78,8 @@ export function defineOAuthAtlassianEventHandler({
78
78
  });
79
79
  if (user.account_status === "inactive") {
80
80
  const error = createError({
81
- statusCode: 403,
82
- statusMessage: "Atlassian account is inactive",
81
+ status: 403,
82
+ message: "Atlassian account is inactive",
83
83
  data: { accountStatus: user.account_status }
84
84
  });
85
85
  if (!onError) throw error;
@@ -87,8 +87,8 @@ export function defineOAuthAtlassianEventHandler({
87
87
  }
88
88
  if (!user.email_verified) {
89
89
  const error = createError({
90
- statusCode: 400,
91
- statusMessage: "Email address is not verified",
90
+ status: 400,
91
+ message: "Email address is not verified",
92
92
  data: { email: user.email }
93
93
  });
94
94
  if (!onError) throw error;
@@ -57,4 +57,4 @@ export interface OAuthAuth0Config {
57
57
  */
58
58
  redirectURL?: string;
59
59
  }
60
- export declare function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
60
+ export declare function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>): import("nuxt/server").EventHandler<Promise<any>>;
@@ -1,11 +1,11 @@
1
- import { eventHandler, getQuery, sendRedirect } from "h3";
1
+ import { defineEventHandler, getQuery, sendRedirect, useRuntimeConfig } from "nuxt/server";
2
+ import { $fetch } from "ofetch";
2
3
  import { withQuery } from "ufo";
3
4
  import { defu } from "defu";
4
5
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, handleInvalidState, handleState, requestAccessToken } from "../utils.js";
5
- import { useRuntimeConfig } from "#imports";
6
6
  export function defineOAuthAuth0EventHandler({ config, onSuccess, onError }) {
7
- return eventHandler(async (event) => {
8
- config = defu(config, useRuntimeConfig(event).oauth?.auth0, {
7
+ return defineEventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig().oauth?.auth0, {
9
9
  authorizationParams: {}
10
10
  });
11
11
  if (!config.clientId || !config.clientSecret || !config.domain) {
@@ -27,4 +27,4 @@ export interface OAuthAuthentikConfig {
27
27
  */
28
28
  scope?: string[];
29
29
  }
30
- export declare function defineOAuthAuthentikEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuthentikConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
30
+ export declare function defineOAuthAuthentikEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuthentikConfig>): import("nuxt/server").EventHandler<Promise<any>>;