@strivacity/sdk-nuxt 3.0.2 → 4.0.0-beta.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 (144) hide show
  1. package/.nuxt/app-component.mjs +1 -1
  2. package/.nuxt/app.config.mjs +21 -0
  3. package/.nuxt/component-names.mjs +1 -1
  4. package/.nuxt/components.d.ts +48 -48
  5. package/.nuxt/components.islands.mjs +6 -2
  6. package/.nuxt/error-component.mjs +1 -1
  7. package/.nuxt/fetch.d.ts +1 -0
  8. package/.nuxt/fetch.mjs +4 -3
  9. package/.nuxt/imports.d.ts +15 -5
  10. package/.nuxt/imports.mjs +15 -5
  11. package/.nuxt/island-renderer.mjs +2 -0
  12. package/.nuxt/middleware.mjs +3 -1
  13. package/.nuxt/nuxt.config.mjs +103 -0
  14. package/.nuxt/nuxt.d.ts +3 -4
  15. package/.nuxt/nuxt.node.d.ts +4 -4
  16. package/.nuxt/nuxt.shared.d.ts +3 -1
  17. package/.nuxt/plugins.client.mjs +10 -14
  18. package/.nuxt/plugins.server.mjs +6 -6
  19. package/.nuxt/root-component.mjs +1 -1
  20. package/.nuxt/route-rules.mjs +22 -0
  21. package/.nuxt/test-component-wrapper.mjs +1 -1
  22. package/.nuxt/tsconfig.app.json +211 -0
  23. package/.nuxt/tsconfig.json +212 -0
  24. package/.nuxt/tsconfig.node.json +117 -0
  25. package/.nuxt/tsconfig.server.json +161 -0
  26. package/.nuxt/tsconfig.shared.json +165 -0
  27. package/.nuxt/types/app.config.d.ts +35 -0
  28. package/.nuxt/types/components.d.ts +48 -48
  29. package/.nuxt/types/imports.d.ts +1 -1
  30. package/.nuxt/types/layouts.d.ts +16 -0
  31. package/.nuxt/types/modules.d.ts +20 -20
  32. package/.nuxt/types/nitro-imports.d.ts +9 -4
  33. package/.nuxt/types/nitro-layouts.d.ts +17 -0
  34. package/.nuxt/types/nitro-nuxt.d.ts +19 -5
  35. package/.nuxt/types/nitro-routes.d.ts +25 -1
  36. package/.nuxt/types/plugins.d.ts +8 -9
  37. package/.nuxt/types/runtime-config.d.ts +6 -12
  38. package/.nuxt/types/shared-app.config.d.ts +28 -0
  39. package/.nuxt/types/shared-imports.d.ts +2 -0
  40. package/.nuxt/unhead-options.mjs +2 -1
  41. package/.nuxt/unhead.config.mjs +1 -0
  42. package/CHANGELOG.md +16 -14
  43. package/README.md +1577 -406
  44. package/build.config.ts +5 -0
  45. package/dist/module.d.mts +3 -193
  46. package/dist/module.json +2 -2
  47. package/dist/module.mjs +78 -57
  48. package/dist/module.mjs.map +1 -1
  49. package/dist/runtime/composables/use-native-login-context.d.ts +10 -0
  50. package/dist/runtime/composables/use-native-login-context.js +9 -0
  51. package/dist/runtime/composables/use-native-login.d.ts +10 -0
  52. package/dist/runtime/composables/use-native-login.js +143 -0
  53. package/dist/runtime/composables/use-session.d.ts +8 -0
  54. package/dist/runtime/composables/use-session.js +2 -0
  55. package/dist/runtime/composables/use-strivacity.d.ts +9 -0
  56. package/dist/runtime/composables/use-strivacity.js +77 -0
  57. package/dist/runtime/middleware/auth.server.d.ts +2 -0
  58. package/dist/runtime/middleware/auth.server.js +12 -0
  59. package/dist/runtime/server/api/auth/backchannel-logout.get.d.ts +2 -0
  60. package/dist/runtime/server/api/auth/backchannel-logout.get.js +2 -0
  61. package/dist/runtime/server/api/auth/callback.get.d.ts +2 -0
  62. package/dist/runtime/server/api/auth/callback.get.js +2 -0
  63. package/dist/runtime/server/api/auth/entry.get.d.ts +2 -0
  64. package/dist/runtime/server/api/auth/entry.get.js +2 -0
  65. package/dist/runtime/server/api/auth/login.get.d.ts +2 -0
  66. package/dist/runtime/server/api/auth/login.get.js +2 -0
  67. package/dist/runtime/server/api/auth/logout.get.d.ts +2 -0
  68. package/dist/runtime/server/api/auth/logout.get.js +2 -0
  69. package/dist/runtime/server/api/auth/refresh.get.d.ts +2 -0
  70. package/dist/runtime/server/api/auth/refresh.get.js +2 -0
  71. package/dist/runtime/server/api/auth/register.get.d.ts +2 -0
  72. package/dist/runtime/server/api/auth/register.get.js +2 -0
  73. package/dist/runtime/server/api/auth/revoke.get.d.ts +2 -0
  74. package/dist/runtime/server/api/auth/revoke.get.js +2 -0
  75. package/dist/runtime/server/composables/use-strivacity.d.ts +2 -0
  76. package/dist/runtime/server/composables/use-strivacity.js +9 -0
  77. package/dist/runtime/server/plugins/auth.server.d.ts +10 -0
  78. package/dist/runtime/server/plugins/auth.server.js +66 -0
  79. package/dist/runtime/server/utils/helpers.d.ts +18 -0
  80. package/dist/runtime/server/utils/helpers.js +39 -0
  81. package/dist/runtime/storages/core.d.ts +1 -0
  82. package/dist/runtime/storages/core.js +13 -0
  83. package/dist/runtime/storages/default.d.ts +21 -0
  84. package/dist/runtime/storages/default.js +71 -0
  85. package/dist/runtime/types.d.ts +219 -0
  86. package/dist/runtime/types.js +1 -0
  87. package/dist/runtime/utils/core.d.ts +1 -0
  88. package/dist/runtime/utils/core.js +64 -0
  89. package/dist/runtime/utils/noop.d.ts +1 -0
  90. package/dist/runtime/utils/noop.js +3 -0
  91. package/dist/types.d.mts +4 -16
  92. package/eslint.config.mjs +5 -0
  93. package/nuxt.config.ts +5 -0
  94. package/package.json +31 -5
  95. package/project.json +44 -0
  96. package/src/module.ts +107 -0
  97. package/src/runtime/composables/use-native-login-context.ts +20 -0
  98. package/src/runtime/composables/use-native-login.ts +182 -0
  99. package/src/runtime/composables/use-session.ts +10 -0
  100. package/src/runtime/composables/use-strivacity.ts +95 -0
  101. package/src/runtime/middleware/auth.server.ts +16 -0
  102. package/src/runtime/server/api/auth/backchannel-logout.get.ts +3 -0
  103. package/src/runtime/server/api/auth/callback.get.ts +3 -0
  104. package/src/runtime/server/api/auth/entry.get.ts +3 -0
  105. package/src/runtime/server/api/auth/login.get.ts +3 -0
  106. package/src/runtime/server/api/auth/logout.get.ts +3 -0
  107. package/src/runtime/server/api/auth/refresh.get.ts +3 -0
  108. package/src/runtime/server/api/auth/register.get.ts +3 -0
  109. package/src/runtime/server/api/auth/revoke.get.ts +3 -0
  110. package/src/runtime/server/composables/use-strivacity.ts +13 -0
  111. package/src/runtime/server/plugins/auth.server.ts +84 -0
  112. package/src/runtime/server/utils/helpers.ts +67 -0
  113. package/src/runtime/storages/core.ts +13 -0
  114. package/src/runtime/storages/default.ts +111 -0
  115. package/src/runtime/types.ts +289 -0
  116. package/src/runtime/utils/core.ts +64 -0
  117. package/src/runtime/utils/noop.ts +3 -0
  118. package/testing/stubs/app.ts +6 -0
  119. package/testing/stubs/imports.ts +19 -0
  120. package/testing/stubs/options-httpClient.ts +8 -0
  121. package/testing/stubs/options-logging.ts +8 -0
  122. package/testing/stubs/options-stateStorage.ts +8 -0
  123. package/testing/stubs/options-storage.ts +8 -0
  124. package/testing/tests/composables.spec.ts +512 -0
  125. package/testing/tests/server/helpers.spec.ts +86 -0
  126. package/testing/tests/server/plugin.spec.ts +138 -0
  127. package/testing/tests/server/routes.spec.ts +34 -0
  128. package/testing/tests/server/use-strivacity.spec.ts +22 -0
  129. package/testing/tests/storages-default.spec.ts +151 -0
  130. package/testing/tests/storages.spec.ts +16 -0
  131. package/testing/tests/utils.spec.ts +10 -0
  132. package/testing/utils/common.ts +33 -0
  133. package/testing/utils/http.ts +58 -0
  134. package/tsconfig.json +3 -0
  135. package/vite.config.mts +20 -0
  136. package/.nuxt/strivacity-sdk-logging.d.ts +0 -13
  137. package/.nuxt/strivacity-sdk-logging.mjs +0 -1
  138. package/.nuxt/strivacity-sdk-storage.d.ts +0 -10
  139. package/.nuxt/strivacity-sdk-storage.mjs +0 -1
  140. package/dist/runtime/composables.d.ts +0 -12
  141. package/dist/runtime/composables.js +0 -83
  142. package/dist/runtime/login-renderer.vue +0 -174
  143. package/dist/runtime/login-renderer.vue.d.ts +0 -39
  144. /package/.nuxt/manifest/meta/{b47927c0-b36b-4d2c-b650-8d55c5ec676a.json → cb11df97-b0c9-4a91-9d4b-60c25c918599.json} +0 -0
package/README.md CHANGED
@@ -1,388 +1,1488 @@
1
1
  # @strivacity/sdk-nuxt
2
2
 
3
- A Nuxt 3 module that integrates Strivacity's policy-driven authentication journeys into your application using the OAuth 2.0 PKCE flow. Supports `redirect`, `popup`, `native`, and `embedded` modes.
3
+ Nuxt SDK for [Strivacity](https://www.strivacity.com) - adds PKCE-protected OIDC authentication to your Nuxt application. Ships as a single Nuxt module: a client SDK (auto-imported composables) plus a backend-for-frontend ([BFF](../../README.md#bff)) Server SDK that registers its own Nitro routes and middleware - no separate backend required.
4
+
5
+ Built on top of [@strivacity/sdk-core](../sdk-core) - see the [core SDK documentation](../sdk-core/README.md) for detailed information about authentication flows, configuration options, and advanced features.
6
+
7
+ **See also:**
8
+ - [Full Documentation](https://docs.strivacity.com/reference/overview) - Complete guide for all authentication modes
9
+ - [Example App](../../apps/nuxt) - Working Nuxt example covering both client-managed and server-managed sessions
10
+ - [Core SDK](../sdk-core/README.md) - Framework-agnostic SDK documentation
11
+
12
+ ## Table of contents
13
+
14
+ - [Prerequisites](#prerequisites)
15
+ - [Installation](#installation)
16
+ - [Choosing a mode](#choosing-a-mode)
17
+ - [Client-managed vs. server-managed sessions](#client-managed-vs-server-managed-sessions)
18
+ - [Quick start](#quick-start)
19
+ - [Client SDK](#client-sdk)
20
+ - [Authentication modes](#authentication-modes)
21
+ - [redirect mode](#redirect-mode)
22
+ - [popup mode](#popup-mode)
23
+ - [embedded mode](#embedded-mode)
24
+ - [native mode](#native-mode)
25
+ - [Composables API](#composables-api)
26
+ - [useStrivacity](#usestrivacity)
27
+ - [useNativeLogin](#usenativelogin)
28
+ - [Server SDK](#server-sdk)
29
+ - [Setup](#setup)
30
+ - [Accessing the session server-side](#accessing-the-session-server-side)
31
+ - [Storages](#server-storages)
32
+ - [Back-channel logout](#back-channel-logout)
33
+ - [Server SDK API reference](#server-sdk-api-reference)
34
+ - [Server configuration reference](#server-configuration-reference)
35
+ - [Route guards](#route-guards)
36
+ - [Shared features](#shared-features)
37
+ - [Configuration reference](#configuration-reference)
38
+ - [Migration guide](#migration-guide)
39
+ - [Vulnerability Reporting](#vulnerability-reporting)
40
+ - [License](#license)
41
+ - [Contributing](#contributing)
4
42
 
5
- See our [Developer Portal](https://www.strivacity.com/learn-support/developer-hub) to get started with developing with the Strivacity product.
43
+ ---
44
+
45
+ ## Prerequisites
46
+
47
+ - Nuxt 4+
48
+ - A Strivacity tenant with an application configured (issuer URL, client ID, redirect URI)
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ npm install @strivacity/sdk-nuxt
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Choosing a mode
61
+
62
+ The SDK supports **four authentication modes**:
63
+
64
+ | Mode | Login UI | Best for |
65
+ | ---------- | ---------------------------------------- | -------------------------------------------- |
66
+ | `redirect` | Strivacity hosted page | Standard web apps |
67
+ | `popup` | Strivacity hosted page in a popup | SPAs that must stay on the current page |
68
+ | `embedded` | Strivacity web components in your page | Branded login inside your own layout |
69
+ | `native` | Your own components driven by flow state | Full UI control, step-by-step form rendering |
70
+
71
+ > All modes use the same PKCE-protected OIDC flow under the hood. The `mode` option only controls where the login UI lives and how the flow state is consumed.
72
+
73
+ ---
74
+
75
+ ## Client-managed vs. server-managed sessions
76
+
77
+ The SDK supports two session strategies, selectable per app via a single option:
78
+
79
+ | Strategy | Tokens live in | Best for |
80
+ | -------- | --------------- | -------- |
81
+ | **Client-managed** | Browser storage (`localStorage` by default) | Simple SPAs that don't need to hide tokens from the browser |
82
+ | **Server-managed (BFF)** | Server-side storage (encrypted http-only cookies by default) via the [Server SDK](#server-sdk) | Apps that need to keep tokens inaccessible to client-side JavaScript, sign requests server-side, or add custom server-side validation |
83
+
84
+ Set `serverSessionUri` on the shared SDK options to switch the client SDK into server-managed mode - login requests are then routed through your own server endpoint instead of the SDK talking to the IDP directly, and tokens are never read from or written to client-side storage. See [Server-side session management](../sdk-core/README.md#server-side-session-management) in the core SDK docs for how this works under the hood. Both strategies are shown side by side below.
85
+
86
+ ---
87
+
88
+ ## Quick start
89
+
90
+ ### 1. Register the module
91
+
92
+ ```ts
93
+ // nuxt.config.ts
94
+ export default defineNuxtConfig({
95
+ modules: ['@strivacity/sdk-nuxt'],
96
+ });
97
+ ```
98
+
99
+ ### 2. Configure it
100
+
101
+ Client and server options live together under a single `strivacity` key - the module automatically strips `secret` and the storage/logging/HTTP-client factory paths before exposing the rest to the client:
102
+
103
+ ```ts
104
+ // nuxt.config.ts
105
+ export default defineNuxtConfig({
106
+ modules: ['@strivacity/sdk-nuxt'],
107
+ strivacity: {
108
+ mode: 'redirect', // authentication mode
109
+ issuer: 'https://<YOUR_TENANT_DOMAIN>', // OIDC provider URL
110
+ clientId: 'YOUR_CLIENT_ID', // OAuth2 client ID
111
+ redirectUri: 'https://your-app.example.com/callback', // callback URL after authentication
112
+ scopes: ['openid', 'profile', 'email'], // requested user permissions/data
113
+
114
+ serverSessionUri: '/auth/login', // Omit this line entirely for client-managed sessions
115
+ secret: process.env.SECRET, // http-only cookie encryption key (random 32+ characters), required for server-managed sessions
116
+ postLoginRedirectUri: '/profile',
117
+ },
118
+ });
119
+ ```
120
+
121
+ The module automatically:
122
+ - registers `/auth/login`, `/auth/register`, `/auth/callback`, `/auth/refresh`, `/auth/revoke`, `/auth/entry`, `/auth/logout`, and `/auth/backchannel-logout` as Nitro server routes (see the table below)
123
+ - auto-imports `useStrivacity()`/`useNativeLogin()` on the client and `useStrivacity(event)` on the server - no import statements needed anywhere
124
+ - registers a global route middleware that resolves the session server-side before every render, so there's no separate provider component or session-prop wiring to do yourself
125
+
126
+ | Method | Path | Description | Response |
127
+ | ------ | --------------------------- | ---------------------------------------------------------- | -------------------------------------------- |
128
+ | `GET` | `/auth/login` | Starts the login flow and redirects to the IDP | `302` redirect to IDP |
129
+ | `GET` | `/auth/register` | Starts the registration flow and redirects to the IDP | `302` redirect to IDP |
130
+ | `GET` | `/auth/callback` | Completes authentication (handles the IDP callback) | `302` redirect or popup close script |
131
+ | `GET` | `/auth/refresh` | Refreshes the access token | `204 No Content` or `302` redirect |
132
+ | `GET` | `/auth/revoke` | Revokes tokens and clears the session | `204 No Content` |
133
+ | `GET` | `/auth/logout` | Ends the session and redirects to the IDP logout page | `302` redirect to IDP logout |
134
+ | `GET` | `/auth/entry` | Handles external flow entry (e.g., password reset link) - embedded/native modes only | JSON with session data |
135
+ | `POST` | `/auth/backchannel-logout` | Processes back-channel logout requests from the IDP | `204 No Content` |
136
+
137
+ > The `/auth` prefix and route names come from `authUrlPrefix` - see [Server configuration reference](#server-configuration-reference).
138
+
139
+ ---
140
+
141
+ ## Client SDK
142
+
143
+ ### Authentication modes
144
+
145
+ #### redirect mode
146
+
147
+ > For details on how this mode works, see the [hosted journey documentation](https://docs.strivacity.com/reference/hosted-journey).
148
+
149
+ The current browser tab navigates to the Strivacity-hosted login page and back to the configured `redirectUri` after authentication.
150
+
151
+ ##### Login
152
+
153
+ **Client-managed sessions**:
154
+
155
+ Call this to start the login flow. It redirects the user to the Strivacity login page in the current browser tab, where they authenticate.
156
+
157
+ ```vue
158
+ <!-- app/pages/login.vue -->
159
+ <script setup lang="ts">
160
+ import type { RedirectFlow } from '@strivacity/sdk-nuxt';
161
+
162
+ const { login } = useStrivacity<RedirectFlow>();
163
+
164
+ onMounted(() => {
165
+ void login({
166
+ // Optional parameters
167
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
168
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
169
+ audiences: ['https://api.example.com'], // target resources for the access token
170
+ });
171
+ });
172
+ </script>
173
+
174
+ <template>
175
+ <section>
176
+ <h1>Redirecting to login...</h1>
177
+ </section>
178
+ </template>
179
+ ```
180
+
181
+ **Server-managed sessions**:
182
+
183
+ Skip the client SDK entirely and redirect straight to `/auth/login` - the Server SDK builds the authorization request and redirects to the IDP. Use `navigateTo` with `external: true` so it's a real HTTP redirect rather than a client-side route match attempt:
184
+
185
+ ```vue
186
+ <!-- app/pages/login.vue -->
187
+ <script setup lang="ts">
188
+ await navigateTo('/auth/login', { external: true });
189
+ </script>
190
+ ```
191
+
192
+ ##### Handle the callback
193
+
194
+ **Client-managed sessions**:
195
+
196
+ Call this on your redirect URI route after the IDP sends the user back. It parses the query parameters from the callback URL, verifies the state matches what was stored during login (CSRF protection), exchanges the authorization code for tokens using PKCE, validates the ID token, and stores the session in the [configured storage](../sdk-core/README.md#storages).
197
+
198
+ ```vue
199
+ <!-- app/pages/callback.vue -->
200
+ <script setup lang="ts">
201
+ const router = useRouter();
202
+ const { handleCallback } = useStrivacity();
203
+ const searchParams = new URLSearchParams(globalThis.window?.location.search);
204
+
205
+ onMounted(async () => {
206
+ if (searchParams.get('error') || searchParams.get('error_description')) {
207
+ await router.replace(`/error?${searchParams.toString()}`);
208
+ return;
209
+ }
210
+
211
+ try {
212
+ await handleCallback();
213
+ await router.push('/profile');
214
+ } catch (error) {
215
+ await router.push(`/error?message=${encodeURIComponent(error instanceof Error ? error.message : 'Unknown error')}`);
216
+ }
217
+ });
218
+ </script>
219
+
220
+ <template>
221
+ <section>
222
+ <h1>Logging in...</h1>
223
+ </section>
224
+ </template>
225
+ ```
226
+
227
+ **Server-managed sessions**:
228
+
229
+ Forward the callback query string to `/auth/callback` - the Server SDK completes the code exchange and redirects to `postLoginRedirectUri`:
230
+
231
+ ```vue
232
+ <!-- app/pages/callback.vue -->
233
+ <script setup lang="ts">
234
+ const route = useRoute();
235
+
236
+ await navigateTo({ path: '/auth/callback', query: route.query }, { redirectCode: 301, external: true });
237
+ </script>
238
+ ```
239
+
240
+ ##### Registration
241
+
242
+ **Client-managed sessions**:
243
+
244
+ Call this to start the registration flow. It works the same way as `login()` but opens the registration form instead.
245
+
246
+ ```vue
247
+ <!-- app/pages/register.vue -->
248
+ <script setup lang="ts">
249
+ import type { RedirectFlow } from '@strivacity/sdk-nuxt';
250
+
251
+ const { register } = useStrivacity<RedirectFlow>();
252
+
253
+ onMounted(() => {
254
+ void register({
255
+ loginHint: 'user@example.com',
256
+ });
257
+ });
258
+ </script>
259
+
260
+ <template>
261
+ <section>
262
+ <h1>Redirecting to registration...</h1>
263
+ </section>
264
+ </template>
265
+ ```
266
+
267
+ **Server-managed sessions**:
268
+
269
+ Skip the client SDK entirely and redirect straight to `/auth/register` - the Server SDK builds the registration request and redirects to the IDP:
270
+
271
+ ```vue
272
+ <!-- app/pages/register.vue -->
273
+ <script setup lang="ts">
274
+ await navigateTo('/auth/register', { external: true });
275
+ </script>
276
+ ```
277
+
278
+ ##### Logout
279
+
280
+ **Client-managed sessions**:
281
+
282
+ Call this to clear the session and redirect to the Strivacity end-session endpoint. After that the user is redirected back to your app at `postLogoutRedirectUri`.
283
+
284
+ ```vue
285
+ <!-- app/pages/logout.vue -->
286
+ <script setup lang="ts">
287
+ const { logout } = useStrivacity();
288
+
289
+ onMounted(() => {
290
+ void logout();
291
+ });
292
+ </script>
293
+
294
+ <template>
295
+ <section>
296
+ <h1>Logging out...</h1>
297
+ </section>
298
+ </template>
299
+ ```
300
+
301
+ **Server-managed sessions**:
302
+
303
+ With `serverSessionUri` configured, redirect to `/auth/logout` instead - the Server SDK clears the session and redirects to the IDP end-session endpoint:
304
+
305
+ ```vue
306
+ <!-- app/pages/logout.vue -->
307
+ <script setup lang="ts">
308
+ await navigateTo('/auth/logout', { external: true });
309
+ </script>
310
+ ```
311
+
312
+ ##### Token management
313
+
314
+ **Client-managed sessions**:
315
+
316
+ Call these methods to manage the session and access token client-side.
317
+
318
+ ```vue
319
+ <script setup lang="ts">
320
+ const { loading, idTokenClaims, accessToken, refreshToken, refresh, revoke } = useStrivacity();
321
+
322
+ async function onRefresh() {
323
+ // Refresh the access token using the refresh token
324
+ await refresh();
325
+ }
326
+
327
+ async function onRevoke() {
328
+ // Revoke all tokens at the authorization server and clear the local session
329
+ await revoke();
330
+ }
331
+ </script>
332
+
333
+ <template>
334
+ <!-- idTokenClaims/accessToken/refreshToken are refs - Vue unwraps them automatically in templates -->
335
+ <div v-if="!loading">
336
+ <button @click="onRefresh">Refresh</button>
337
+ <button @click="onRevoke">Revoke</button>
338
+ <pre>{{ JSON.stringify({ idTokenClaims, accessToken, refreshToken }, null, 2) }}</pre>
339
+ </div>
340
+ </template>
341
+ ```
342
+
343
+ **Server-managed sessions**:
344
+
345
+ With `serverSessionUri` configured, tokens are refreshed/revoked by the Server SDK - trigger it by navigating to the auth routes, then let it redirect back:
346
+
347
+ ```vue
348
+ <script setup lang="ts">
349
+ function onRefresh() {
350
+ // sdk.refreshSession() runs server-side, then redirects back to returnTo
351
+ globalThis.location.href = '/auth/refresh?returnTo=/profile';
352
+ }
353
+
354
+ function onRevoke() {
355
+ // sdk.revokeSession() runs server-side, then redirects to postLogoutRedirectUri
356
+ globalThis.location.href = '/auth/revoke';
357
+ }
358
+ </script>
359
+
360
+ <template>
361
+ <div>
362
+ <button @click="onRefresh">Refresh</button>
363
+ <button @click="onRevoke">Revoke</button>
364
+ </div>
365
+ </template>
366
+ ```
367
+
368
+ ---
369
+
370
+ #### popup mode
371
+
372
+ > For details on how this mode works, see the [hosted journey documentation](https://docs.strivacity.com/reference/hosted-journey).
373
+
374
+ The Strivacity login page opens in a separate window or tab. After authentication the opened window or tab closes itself and the parent page receives the session - no full-page navigation required.
375
+
376
+ ##### Login
377
+
378
+ **Client-managed sessions**:
379
+
380
+ Call this to start the login flow. It opens a popup window by default with the Strivacity login page, where the user authenticates. After that the popup closes itself and the session is stored in the [configured storage](../sdk-core/README.md#storages).
381
+
382
+ By default a centered popup window opens. Pass `popupWindowTarget` to change where the window opens, and `popupWindowFeatures` to control its size and position:
383
+
384
+ ```vue
385
+ <!-- app/pages/login.vue -->
386
+ <script setup lang="ts">
387
+ import type { PopupFlow } from '@strivacity/sdk-nuxt';
388
+
389
+ const router = useRouter();
390
+ const { login } = useStrivacity<PopupFlow>();
391
+
392
+ onMounted(async () => {
393
+ try {
394
+ await login({
395
+ // Optional parameters
396
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
397
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
398
+ audiences: ['https://api.example.com'], // target resources for the access token
399
+ popupWindowTarget: '_blank', // any valid browsing context name
400
+ popupWindowFeatures: {
401
+ width: 500,
402
+ height: 700,
403
+ left: 100,
404
+ top: 100,
405
+ toolbar: false,
406
+ location: false,
407
+ resizable: true,
408
+ scrollbars: true,
409
+ },
410
+ })
411
+ .then(() => navigate('/profile'))
412
+ .catch((error) => navigate(`/error?message=${encodeURIComponent(error.message)}`));
413
+ });
414
+
415
+ return (
416
+ <section>
417
+ <h1>Opening login popup...</h1>
418
+ </section>
419
+ );
420
+ }
421
+ ```
422
+
423
+ **Server-managed sessions**:
424
+
425
+ Popup mode always needs client-side JavaScript to open the window, so there's no server-only alternative here.
426
+
427
+ ##### Handle the callback
428
+
429
+ **Client-managed sessions**:
430
+
431
+ The popup resolves automatically - no callback route is needed. Token exchange happens inside the popup and the result is posted back to the opener window.
432
+
433
+ **Server-managed sessions**:
434
+
435
+ Same as client managed - the popup's internal callback request is also transparently proxied through `/auth/callback`, and the result is posted back to the opener window exactly the same way.
436
+
437
+ ##### Registration
438
+
439
+ **Client-managed sessions**:
440
+
441
+ Call this to start the registration flow. It works the same way as `login()` but opens the registration form instead.
442
+
443
+ ```vue
444
+ <!-- app/pages/register.vue -->
445
+ <script setup lang="ts">
446
+ import type { PopupFlow } from '@strivacity/sdk-nuxt';
447
+
448
+ const router = useRouter();
449
+ const { register } = useStrivacity<PopupFlow>();
450
+
451
+ onMounted(async () => {
452
+ try {
453
+ await register({
454
+ // Optional parameters
455
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
456
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
457
+ audiences: ['https://api.example.com'], // target resources for the access token
458
+ popupWindowTarget: '_blank', // any valid browsing context name
459
+ popupWindowFeatures: {
460
+ width: 500,
461
+ height: 700,
462
+ left: 100,
463
+ top: 100,
464
+ toolbar: false,
465
+ location: false,
466
+ resizable: true,
467
+ scrollbars: true,
468
+ },
469
+ });
470
+ await router.push('/profile');
471
+ } catch (error) {
472
+ await router.push(`/error?message=${encodeURIComponent(error instanceof Error ? error.message : 'Unknown error')}`);
473
+ }
474
+ });
475
+ </script>
476
+
477
+ <template>
478
+ <section>
479
+ <h1>Opening registration popup...</h1>
480
+ </section>
481
+ </template>
482
+ ```
483
+
484
+ **Server-managed sessions**:
485
+
486
+ Popup mode always needs client-side JavaScript to open the window, so there's no server-only alternative here.
487
+
488
+ ##### Logout
489
+
490
+ **Client-managed sessions**:
491
+
492
+ Call this to clear the session and redirect to the Strivacity end-session endpoint. After that the user is redirected back to your app at `postLogoutRedirectUri`.
493
+
494
+ ```vue
495
+ <!-- app/pages/logout.vue -->
496
+ <script setup lang="ts">
497
+ const { logout } = useStrivacity();
498
+
499
+ onMounted(() => {
500
+ void logout();
501
+ });
502
+ </script>
503
+
504
+ <template>
505
+ <section>
506
+ <h1>Logging out...</h1>
507
+ </section>
508
+ </template>
509
+ ```
510
+
511
+ **Server-managed sessions**:
512
+
513
+ With `serverSessionUri` configured, redirect to `/auth/logout` instead - the Server SDK clears the session and redirects to the IDP end-session endpoint:
514
+
515
+ ```vue
516
+ <!-- app/pages/logout.vue -->
517
+ <script setup lang="ts">
518
+ await navigateTo('/auth/logout', { external: true });
519
+ </script>
520
+ ```
521
+
522
+ ##### Token management
523
+
524
+ **Client-managed sessions**:
525
+
526
+ Call these methods to manage the session and access token client-side.
527
+
528
+ ```vue
529
+ <script setup lang="ts">
530
+ const { loading, idTokenClaims, accessToken, refreshToken, refresh, revoke } = useStrivacity();
531
+
532
+ async function onRefresh() {
533
+ // Refresh the access token using the refresh token
534
+ await refresh();
535
+ }
536
+
537
+ async function onRevoke() {
538
+ // Revoke all tokens at the authorization server and clear the local session
539
+ await revoke();
540
+ }
541
+ </script>
542
+
543
+ <template>
544
+ <div v-if="!loading">
545
+ <button @click="onRefresh">Refresh</button>
546
+ <button @click="onRevoke">Revoke</button>
547
+ <pre>{{ JSON.stringify({ idTokenClaims, accessToken, refreshToken }, null, 2) }}</pre>
548
+ </div>
549
+ </template>
550
+ ```
551
+
552
+ **Server-managed sessions**:
553
+
554
+ With `serverSessionUri` configured, tokens are refreshed/revoked by the Server SDK - trigger it by navigating to the auth routes, then let it redirect back:
555
+
556
+ ```vue
557
+ <script setup lang="ts">
558
+ function onRefresh() {
559
+ // sdk.refreshSession() runs server-side, then redirects back to returnTo
560
+ globalThis.location.href = '/auth/refresh?returnTo=/profile';
561
+ }
562
+
563
+ function onRevoke() {
564
+ // sdk.revokeSession() runs server-side, then redirects to postLogoutRedirectUri
565
+ globalThis.location.href = '/auth/revoke';
566
+ }
567
+ </script>
568
+
569
+ <template>
570
+ <div>
571
+ <button @click="onRefresh">Refresh</button>
572
+ <button @click="onRevoke">Revoke</button>
573
+ </div>
574
+ </template>
575
+ ```
576
+
577
+ ---
578
+
579
+ #### embedded mode
580
+
581
+ > For details on how this mode works, see the [embedded journey documentation](https://docs.strivacity.com/reference/embedded-journey).
582
+
583
+ The login UI renders inside your own page using Strivacity web components (`<sty-login>`, `<sty-notifications>`, `<sty-language-selector>`). The component bundle isn't an npm package - load it dynamically from your Strivacity tenant cluster once, on the login route:
584
+
585
+ ##### Login / Register
586
+
587
+ **Client-managed sessions**:
588
+
589
+ ```vue
590
+ <!-- app/pages/login.vue -->
591
+ <script setup lang="ts">
592
+ const { sdk } = useStrivacity();
593
+ const searchParams = new URLSearchParams(globalThis.window?.location.search);
594
+
595
+ const params = {
596
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
597
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
598
+ audiences: ['https://api.example.com'], // target resources for the access token
599
+ language: 'en-US', // set the UI language (BCP 47 language tag)
600
+ prompt: 'login', // use 'create' to open the registration flow instead
601
+ };
602
+ // Optional: Resume a session started from an entry URL (e.g., password reset)
603
+ const sessionId = ref(searchParams.get('session_id'));
604
+ const shortAppId = ref(searchParams.get('short_app_id'));
605
+ const language = ref(searchParams.get('language') ?? globalThis.navigator?.language);
606
+
607
+ // injectScript loads the <sty-login>/<sty-notifications>/<sty-language-selector>
608
+ // custom element definitions from the auth server
609
+ injectScript('sty-components', `${sdk.options.issuer}/assets/components/bundle.js`);
610
+
611
+ function onLogin() {
612
+ globalThis.location.href = '/profile';
613
+ }
614
+
615
+ function onClose() {
616
+ globalThis.location.reload();
617
+ }
618
+
619
+ function onError(errorOrEvent: Error | CustomEvent<string>) {
620
+ const message = errorOrEvent instanceof Error ? errorOrEvent.message : errorOrEvent.detail;
621
+ globalThis.location.href = `/error?message=${encodeURIComponent(message)}`;
622
+ }
623
+ </script>
624
+
625
+ <template>
626
+ <section>
627
+ <sty-notifications></sty-notifications>
628
+ <sty-login
629
+ :params.prop="params"
630
+ :sessionId="sessionId"
631
+ :shortAppId="shortAppId"
632
+ :lang="language"
633
+ @login="onLogin"
634
+ @close="onClose"
635
+ @error="onError"
636
+ ></sty-login>
637
+ <sty-language-selector></sty-language-selector>
638
+ </section>
639
+ </template>
640
+ ```
641
+
642
+ **Server-managed sessions**:
643
+
644
+ With `serverSessionUri` configured the code above is unchanged - the `<sty-login>` component's internal requests are transparently proxied through `/auth/login`/`/auth/register` instead of going straight to the IDP.
645
+
646
+ ##### Controlling when the flow starts
647
+
648
+ By default the login flow starts automatically as soon as `<sty-login>` connects to the DOM. Add the `lazy` attribute to take manual control, then call `start()` when ready. `start()` accepts an optional params object forwarded to the authorization request, or you can set params via the `params` property before calling it:
649
+
650
+ ```vue
651
+ <!-- app/pages/login.vue -->
652
+ <script setup lang="ts">
653
+ import type { LoginComponent } from '@strivacity/sdk-nuxt';
654
+
655
+ const router = useRouter();
656
+ const loginRef = ref<LoginComponent>();
657
+
658
+ async function onStartClick() {
659
+ await loginRef.value?.start({
660
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
661
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
662
+ audiences: ['https://api.example.com'], // target resources for the access token
663
+ language: 'en-US', // set the UI language (BCP 47 language tag)
664
+ prompt: 'login', // use 'create' to open the registration flow instead
665
+ });
666
+ }
667
+ </script>
668
+
669
+ <template>
670
+ <section>
671
+ <sty-notifications></sty-notifications>
672
+ <button @click="onStartClick">Continue to login</button>
673
+ <sty-login ref="loginRef" lazy @login="router.push('/profile')"></sty-login>
674
+ <sty-language-selector></sty-language-selector>
675
+ </section>
676
+ </template>
677
+ ```
678
+
679
+ You can also set params via the `params` property before calling `start()`:
680
+
681
+ ```vue
682
+ <!-- app/pages/login.vue -->
683
+ <script setup lang="ts">
684
+ import type { LoginComponent } from '@strivacity/sdk-nuxt';
685
+
686
+ const loginRef = ref<LoginComponent>();
687
+
688
+ async function onStartClick() {
689
+ if (!loginRef.value) {
690
+ return;
691
+ }
692
+
693
+ loginRef.value.params = {
694
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
695
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
696
+ audiences: ['https://api.example.com'], // target resources for the access token
697
+ language: 'en-US', // set the UI language (BCP 47 language tag)
698
+ prompt: 'login', // use 'create' to open the registration flow instead
699
+ };
700
+ await loginRef.value.start();
701
+ }
702
+ </script>
703
+
704
+ <template>
705
+ <sty-login ref="loginRef" lazy></sty-login>
706
+ <button @click="onStartClick">Start Login</button>
707
+ </template>
708
+ ```
709
+
710
+ ##### Login events
711
+
712
+ The `<sty-login>` element dispatches `login`, `close`, and `error` custom events. Listen to them using Vue's `@` directive:
713
+
714
+ ```vue
715
+ <!-- app/pages/login.vue -->
716
+ <script setup lang="ts">
717
+ const router = useRouter();
718
+
719
+ async function onLogin() {
720
+ // User authenticated - navigate to a protected page
721
+ await router.push('/profile');
722
+ }
723
+
724
+ function onClose() {
725
+ // User cancelled or closed the login flow
726
+ globalThis.location.reload();
727
+ }
728
+
729
+ async function onError(event: CustomEvent) {
730
+ // A fatal error occurred - the message is available in event.detail
731
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
732
+ }
733
+ </script>
734
+
735
+ <template>
736
+ <section>
737
+ <sty-notifications></sty-notifications>
738
+ <sty-login
739
+ @login="onLogin"
740
+ @close="onClose"
741
+ @error="onError"
742
+ ></sty-login>
743
+ <sty-language-selector></sty-language-selector>
744
+ </section>
745
+ </template>
746
+ ```
747
+
748
+ ##### Notification events
749
+
750
+ The components dispatch `notification` events on the `document` that the `<sty-notifications>` component automatically displays. If you don't want to use `<sty-notifications>`, you can listen to these events and handle them yourself:
751
+
752
+ ```vue
753
+ <script setup lang="ts">
754
+ function onNotification(event: Event) {
755
+ const customEvent = event as CustomEvent;
756
+
757
+ if (customEvent.detail.action === 'show') {
758
+ // Add new notification to your custom notification system
759
+ console.log('New notification:', customEvent.detail.notification);
760
+ } else if (customEvent.detail.action === 'clear') {
761
+ console.log('Clear all notifications');
762
+ }
763
+ }
764
+
765
+ onMounted(() => {
766
+ document.addEventListener('notification', onNotification);
767
+ });
768
+
769
+ onUnmounted(() => {
770
+ document.removeEventListener('notification', onNotification);
771
+ });
772
+ </script>
773
+ ```
774
+
775
+ ##### Dynamic language switching
776
+
777
+ The `<sty-language-selector>` component provides a built-in UI for language switching. If you don't want to use it, you can change the UI language dynamically by updating the `lang` property on the `<sty-login>` component:
778
+
779
+ ```vue
780
+ <script setup lang="ts">
781
+ const loginEl = useTemplateRef<HTMLElement & { lang: string }>('loginEl');
782
+ const currentLang = ref('en-US');
783
+
784
+ function changeLanguage(lang: string) {
785
+ currentLang.value = lang;
786
+
787
+ if (loginEl.value) {
788
+ loginEl.value.lang = lang;
789
+ }
790
+ }
791
+ </script>
792
+
793
+ <template>
794
+ <section>
795
+ <div>
796
+ <button @click="changeLanguage('en-US')">English</button>
797
+ <button @click="changeLanguage('fr-FR')">Français</button>
798
+ <button @click="changeLanguage('de-DE')">Deutsch</button>
799
+ </div>
800
+ <sty-login ref="loginEl" :lang="currentLang"></sty-login>
801
+ </section>
802
+ </template>
803
+ ```
804
+
805
+ ##### Handle the callback
806
+
807
+ **Client-managed sessions**:
808
+
809
+ No separate callback route is needed. The `<sty-login>` component handles the entire authentication flow automatically, including token exchange, and dispatches a `login` event when authentication completes successfully.
810
+
811
+ **Server-managed sessions**:
812
+
813
+ Same as client managed - the component's internal callback request is also transparently proxied through `/auth/callback`, with no separate route needed either way.
814
+
815
+ #### Externally-initiated flows (entry)
816
+
817
+ For flows started externally (e.g. a password reset email link), the user lands on the entry URL you configured in your Strivacity application native client settings. Call `entry()` on that landing route to resolve the flow parameters from the IDP (`session_id`, `short_app_id`, `language`).
818
+
819
+ You have two options:
820
+
821
+ **Option 1: Redirect to a separate login route**
822
+
823
+ Forward the parameters as query params to your login route:
824
+
825
+ ```vue
826
+ <!-- app/pages/entry.vue -->
827
+ <script setup lang="ts">
828
+ import type { EmbeddedFlow } from '@strivacity/sdk-nuxt';
829
+
830
+ const { entry } = useStrivacity<EmbeddedFlow>();
831
+ const router = useRouter();
832
+
833
+ onMounted(async () => {
834
+ try {
835
+ const data = await entry();
836
+
837
+ // Redirect to login route with flow parameters
838
+ const params = new URLSearchParams({
839
+ session_id: data.session_id,
840
+ short_app_id: data.short_app_id,
841
+ language: data.language,
842
+ });
843
+ globalThis.location.href = `/login?${params}`;
844
+ } catch (error) {
845
+ await router.push(`/error?message=${encodeURIComponent(error instanceof Error ? error.message : 'Unknown error')}`);
846
+ }
847
+ });
848
+ </script>
849
+
850
+ <template>
851
+ <section>
852
+ <h1>Loading...</h1>
853
+ </section>
854
+ </template>
855
+ ```
856
+
857
+ Then on your login route, read the parameters and pass them to `<sty-login>`:
858
+
859
+ ```vue
860
+ <!-- app/pages/login.vue -->
861
+ <script setup lang="ts">
862
+ const router = useRouter();
863
+ const searchParams = new URLSearchParams(globalThis.window?.location.search);
864
+
865
+ // Read parameters from URL
866
+ const sessionId = searchParams.get('session_id');
867
+ const shortAppId = searchParams.get('short_app_id');
868
+ const language = searchParams.get('language');
869
+
870
+ async function onLogin() {
871
+ // User authenticated - navigate to a protected page
872
+ await router.push('/profile');
873
+ }
874
+
875
+ async function onError(event: CustomEvent) {
876
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
877
+ }
878
+ </script>
879
+
880
+ <template>
881
+ <section>
882
+ <sty-notifications></sty-notifications>
883
+ <sty-login
884
+ :sessionId="sessionId"
885
+ :shortAppId="shortAppId"
886
+ :lang="language"
887
+ @login="onLogin"
888
+ @error="onError"
889
+ ></sty-login>
890
+ <sty-language-selector></sty-language-selector>
891
+ </section>
892
+ </template>
893
+ ```
894
+
895
+ **Option 2: Render login on the entry route**
896
+
897
+ Pass the parameters directly to `<sty-login>` on the same route:
898
+
899
+ ```vue
900
+ <!-- app/pages/entry.vue -->
901
+ <script setup lang="ts">
902
+ import type { EmbeddedFlow } from '@strivacity/sdk-nuxt';
903
+
904
+ const { entry } = useStrivacity<EmbeddedFlow>();
905
+ const router = useRouter();
906
+
907
+ const sessionId = ref<string | null>(null);
908
+ const shortAppId = ref<string | null>(null);
909
+ const language = ref<string | null>(null);
910
+
911
+ onMounted(async () => {
912
+ try {
913
+ const data = await entry();
914
+
915
+ // Set properties for sty-login component
916
+ sessionId.value = data.session_id;
917
+ shortAppId.value = data.short_app_id;
918
+ language.value = data.language;
919
+ } catch (error) {
920
+ await router.push(`/error?message=${encodeURIComponent(error instanceof Error ? error.message : 'Unknown error')}`);
921
+ }
922
+ });
923
+
924
+ async function onLogin() {
925
+ // User authenticated - navigate to a protected page
926
+ await router.push('/profile');
927
+ }
928
+
929
+ async function onError(event: CustomEvent) {
930
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
931
+ }
932
+ </script>
933
+
934
+ <template>
935
+ <section v-if="sessionId">
936
+ <sty-notifications></sty-notifications>
937
+ <sty-login
938
+ :sessionId="sessionId"
939
+ :shortAppId="shortAppId"
940
+ :lang="language"
941
+ @login="onLogin"
942
+ @error="onError"
943
+ ></sty-login>
944
+ <sty-language-selector></sty-language-selector>
945
+ </section>
946
+ <section v-else>
947
+ <h1>Loading...</h1>
948
+ </section>
949
+ </template>
950
+ ```
951
+
952
+ ##### Logout
953
+
954
+ **Client-managed sessions**:
955
+
956
+ Call this to clear the session and redirect to the Strivacity end-session endpoint. After that the user is redirected back to your app at `postLogoutRedirectUri`.
957
+
958
+ ```vue
959
+ <!-- app/pages/logout.vue -->
960
+ <script setup lang="ts">
961
+ const { logout } = useStrivacity();
962
+
963
+ onMounted(() => {
964
+ void logout();
965
+ });
966
+ </script>
967
+
968
+ <template>
969
+ <section>
970
+ <h1>Logging out...</h1>
971
+ </section>
972
+ </template>
973
+ ```
974
+
975
+ **Server-managed sessions**:
976
+
977
+ With `serverSessionUri` configured, redirect to `/auth/logout` instead - the Server SDK clears the session and redirects to the IDP end-session endpoint:
978
+
979
+ ```vue
980
+ <!-- app/pages/logout.vue -->
981
+ <script setup lang="ts">
982
+ await navigateTo('/auth/logout', { external: true });
983
+ </script>
984
+ ```
985
+
986
+ ##### Token management
987
+
988
+ **Client-managed sessions**:
989
+
990
+ Call these methods to manage the session and access token client-side.
991
+
992
+ ```vue
993
+ <script setup lang="ts">
994
+ const { loading, idTokenClaims, accessToken, refreshToken, refresh, revoke } = useStrivacity();
995
+
996
+ async function onRefresh() {
997
+ // Refresh the access token using the refresh token
998
+ await refresh();
999
+ }
1000
+
1001
+ async function onRevoke() {
1002
+ // Revoke all tokens at the authorization server and clear the local session
1003
+ await revoke();
1004
+ }
1005
+ </script>
1006
+
1007
+ <template>
1008
+ <div v-if="!loading">
1009
+ <button @click="onRefresh">Refresh</button>
1010
+ <button @click="onRevoke">Revoke</button>
1011
+ <pre>{{ JSON.stringify({ idTokenClaims, accessToken, refreshToken }, null, 2) }}</pre>
1012
+ </div>
1013
+ </template>
1014
+ ```
1015
+
1016
+ **Server-managed sessions**:
1017
+
1018
+ With `serverSessionUri` configured, tokens are refreshed/revoked by the Server SDK - trigger it by navigating to the auth routes, then let it redirect back:
1019
+
1020
+ ```vue
1021
+ <script setup lang="ts">
1022
+ function onRefresh() {
1023
+ // sdk.refreshSession() runs server-side, then redirects back to returnTo
1024
+ globalThis.location.href = '/auth/refresh?returnTo=/profile';
1025
+ }
1026
+
1027
+ function onRevoke() {
1028
+ // sdk.revokeSession() runs server-side, then redirects to postLogoutRedirectUri
1029
+ globalThis.location.href = '/auth/revoke';
1030
+ }
1031
+ </script>
1032
+
1033
+ <template>
1034
+ <div>
1035
+ <button @click="onRefresh">Refresh</button>
1036
+ <button @click="onRevoke">Revoke</button>
1037
+ </div>
1038
+ </template>
1039
+ ```
6
1040
 
7
- ## Overview
1041
+ ---
8
1042
 
9
- This SDK allows you to integrate Strivacity's policy-driven journeys into your Nuxt 3 application. It registers itself as a Nuxt module and automatically provides the `useStrivacity` composable and `StyLoginRenderer` component throughout your application without needing explicit imports. The SDK uses the OAuth 2.0 PKCE flow to authenticate with Strivacity. For detailed configuration options, available modes, and advanced usage refer to the [`@strivacity/sdk-core` documentation](https://github.com/Strivacity/sdk-js/blob/main/packages/sdk-core/README.md).
1043
+ #### native mode
10
1044
 
11
- ## Demo Application
1045
+ > For details on how this mode works, see the [native journey documentation](https://docs.strivacity.com/reference/native-journey).
12
1046
 
13
- - [Example app](https://github.com/Strivacity/sdk-js/tree/main/apps/nuxt)
1047
+ You build the entire login UI with your own components. `useNativeLogin()` drives a "headless" auth flow: instead of redirecting to a hosted page, the SDK returns a JSON description of the current screen that you render yourself, submit each form step with `submitForm()`, and repeat until the flow finalizes automatically.
14
1048
 
15
- ## Requirements
1049
+ > If you split widget rendering into separate sub-components, they'll need access to the login context returned by `useNativeLogin()` - see [useNativeLogin](#usenativelogin) below.
1050
+ >
1051
+ > The example below shows a simplified custom implementation. For a complete native renderer with all widget types, see the [example app](../../apps/nuxt/app/components/auth/native/NativeLogin.vue).
16
1052
 
17
- - Nuxt: 3+
1053
+ ##### Login / Register
18
1054
 
19
- ## Install
1055
+ **Client-managed sessions**:
20
1056
 
21
- ```bash
22
- npm install @strivacity/sdk-nuxt
1057
+ ```vue
1058
+ <!-- app/pages/login.vue -->
1059
+ <script setup lang="ts">
1060
+ const router = useRouter();
1061
+ const searchParams = new URLSearchParams(globalThis.window?.location.search);
1062
+
1063
+ const { state, forms, messages, loading, submitForm, setFormValue } = useNativeLogin({
1064
+ params: {
1065
+ prompt: 'login', // use 'create' to open the registration flow instead
1066
+ language: 'en-US', // set the UI language (BCP 47 language tag)
1067
+ sdk: 'web-minimal', // rendering mode: 'web-minimal' for simplified rendering, 'web' (default) for full rendering hints and branding
1068
+ sessionId: searchParams.get('session_id'), // pass a session ID to resume an existing flow
1069
+ },
1070
+ onLogin: async () => {
1071
+ await router.push('/profile');
1072
+ },
1073
+ onClose: () => {
1074
+ globalThis.location.reload();
1075
+ },
1076
+ onError: async (error) => {
1077
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
1078
+ },
1079
+ onFallback: (error) => {
1080
+ // Fallback to hosted journey if native widget not supported
1081
+ globalThis.location.href = error.url.toString();
1082
+ },
1083
+ onGlobalMessage: (message) => {
1084
+ alert(message.text);
1085
+ },
1086
+ });
1087
+ </script>
1088
+
1089
+ <template>
1090
+ <section v-if="loading">
1091
+ <h1>Loading...</h1>
1092
+ </section>
1093
+
1094
+ <section v-else-if="state.screen === 'identifier'">
1095
+ <h2>Sign In</h2>
1096
+ <form @submit.prevent="submitForm('identifier')">
1097
+ <input
1098
+ :value="forms['identifier']?.identifier ?? ''"
1099
+ type="text"
1100
+ placeholder="Email"
1101
+ @input="setFormValue('identifier', 'identifier', ($event.target as HTMLInputElement).value)"
1102
+ />
1103
+ <div v-if="messages['identifier']?.identifier" class="error">
1104
+ {{ messages['identifier'].identifier.text }}
1105
+ </div>
1106
+ <button type="submit">Continue</button>
1107
+ </form>
1108
+ </section>
1109
+
1110
+ <section v-else-if="state.screen === 'password'">
1111
+ <h2>Enter Password</h2>
1112
+ <form @submit.prevent="submitForm('password')">
1113
+ <input
1114
+ :value="forms['password']?.password ?? ''"
1115
+ type="password"
1116
+ placeholder="Password"
1117
+ @input="setFormValue('password', 'password', ($event.target as HTMLInputElement).value)"
1118
+ />
1119
+ <div v-if="messages['password']?.password" class="error">
1120
+ {{ messages['password'].password.text }}
1121
+ </div>
1122
+ <button type="submit">Sign In</button>
1123
+ </form>
1124
+ </section>
1125
+ </template>
23
1126
  ```
24
1127
 
25
- ## Usage
1128
+ **Server-managed sessions**:
26
1129
 
27
- ### Initialization
1130
+ With `serverSessionUri` configured the code above is unchanged - `useNativeLogin`'s internal requests are transparently proxied through your server instead of going straight to the IDP.
28
1131
 
29
- Register the SDK as a Nuxt module in `nuxt.config.ts`:
1132
+ ##### Handle the callback
30
1133
 
31
- ```ts
32
- // nuxt.config.ts
33
- export default defineNuxtConfig({
34
- modules: ['@strivacity/sdk-nuxt'],
35
- strivacity: {
36
- mode: 'redirect', // or 'popup', 'native', 'embedded'
37
- issuer: 'https://<YOUR_DOMAIN>',
38
- scopes: ['openid', 'profile'],
39
- clientId: '<YOUR_CLIENT_ID>',
40
- redirectUri: '<YOUR_REDIRECT_URI>',
41
- },
42
- });
43
- ```
1134
+ **Client-managed sessions**:
44
1135
 
45
- Use the auto-imported `useStrivacity` composable in any component to access authentication state:
1136
+ No separate callback route is needed. Once `state.finalizeUrl` is set, `submitForm()` automatically finalizes the session internally to exchange the authorization code for tokens and store it.
46
1137
 
47
- ```vue
48
- <script setup>
49
- const { loading, isAuthenticated, idTokenClaims } = useStrivacity();
50
- </script>
51
- ```
1138
+ **Server-managed sessions**:
1139
+
1140
+ Same as client managed - finalizing the session also transparently proxies through your server, with no separate route needed either way.
52
1141
 
53
- ### Redirect / Popup mode
1142
+ #### Externally-initiated flows (entry)
54
1143
 
55
- In `redirect` mode the user is taken to the identity provider in the same window; in `popup` mode authentication happens in a popup. Both are initiated the same way from code.
1144
+ For flows started externally (e.g. a password reset email link), the user lands on the entry URL you configured in your Strivacity application native client settings. Call `entry()` on that landing route to resolve the flow parameters from the IDP (`session_id`, `short_app_id`, `language`).
56
1145
 
57
- #### Login page example
1146
+ You have two options:
1147
+
1148
+ **Option 1: Redirect to a separate login route**
58
1149
 
59
1150
  ```vue
60
- <script setup>
61
- import { onMounted } from 'vue';
1151
+ <!-- app/pages/entry.vue -->
1152
+ <script setup lang="ts">
1153
+ import type { NativeFlow } from '@strivacity/sdk-nuxt';
62
1154
 
63
- const { login } = useStrivacity();
1155
+ const { entry } = useStrivacity<NativeFlow>();
1156
+ const router = useRouter();
64
1157
 
65
- onMounted(() => {
66
- login();
1158
+ onMounted(async () => {
1159
+ try {
1160
+ const data = await entry();
1161
+
1162
+ const params = new URLSearchParams({
1163
+ session_id: data.session_id,
1164
+ short_app_id: data.short_app_id,
1165
+ language: data.language,
1166
+ });
1167
+ globalThis.location.href = `/login?${params}`;
1168
+ } catch (error) {
1169
+ await router.push(`/error?message=${encodeURIComponent(error instanceof Error ? error.message : 'Unknown error')}`);
1170
+ }
67
1171
  });
68
1172
  </script>
69
1173
 
70
1174
  <template>
71
1175
  <section>
72
- <h1>Redirecting...</h1>
1176
+ <h1>Loading...</h1>
73
1177
  </section>
74
1178
  </template>
75
1179
  ```
76
1180
 
77
- #### Callback page example
1181
+ Then on your login route, read query parameters from the URL and pass it to `useNativeLogin` to resume the flow, exactly as shown in the Login / Register example above:
78
1182
 
79
- The callback page handles the response from the identity provider. It calls `handleCallback()` and redirects to `/profile` on success:
1183
+ ```vue
1184
+ <!-- app/pages/login.vue -->
1185
+ <script setup lang="ts">
1186
+ const searchParams = new URLSearchParams(globalThis.window?.location.search);
1187
+
1188
+ const { state, forms, messages, loading, submitForm, setFormValue } = useNativeLogin({
1189
+ params: {
1190
+ sessionId: searchParams.get('session_id'),
1191
+ language: searchParams.get('language'),
1192
+ },
1193
+ onLogin: async () => {
1194
+ globalThis.location.href = '/profile';
1195
+ },
1196
+ });
1197
+
1198
+ // ...render based on `state.screen` as shown in the Login / Register example above
1199
+ </script>
1200
+ ```
1201
+
1202
+ **Option 2: Render login on the entry route**
1203
+
1204
+ Call `useNativeLogin` directly inside `app/pages/entry.vue`, feeding it the `session_id` resolved from `entry()` - no redirect needed:
80
1205
 
81
1206
  ```vue
82
- <script setup>
83
- import { onMounted } from 'vue';
1207
+ <!-- app/pages/entry.vue -->
1208
+ <script setup lang="ts">
1209
+ import type { NativeFlow } from '@strivacity/sdk-nuxt';
84
1210
 
85
1211
  const router = useRouter();
86
- const { handleCallback } = useStrivacity();
1212
+ const { loading: sdkLoading, entry } = useStrivacity<NativeFlow>();
1213
+ const sessionId = ref<string | null>(null);
1214
+ const language = ref<string | null>(null);
1215
+ const ready = ref(false);
87
1216
 
88
1217
  onMounted(async () => {
89
1218
  try {
90
- await handleCallback();
91
- await router.push('/profile');
1219
+ const data = await entry();
1220
+
1221
+ sessionId.value = data.session_id;
1222
+ language.value = data.language;
1223
+ ready.value = true;
92
1224
  } catch (error) {
93
- console.error('Error during callback handling:', error);
1225
+ await router.push(`/error?message=${encodeURIComponent(error instanceof Error ? error.message : 'Unknown error')}`);
94
1226
  }
95
1227
  });
1228
+
1229
+ const { state, forms, messages, loading, submitForm, setFormValue } = useNativeLogin({
1230
+ params: { sessionId: sessionId.value ?? undefined, language: language.value ?? undefined },
1231
+ onLogin: async () => {
1232
+ await router.push('/profile');
1233
+ },
1234
+ onError: async (error) => {
1235
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
1236
+ },
1237
+ });
96
1238
  </script>
97
1239
 
98
1240
  <template>
99
- <section>
100
- <h1>Logging in...</h1>
1241
+ <section v-if="!ready || sdkLoading || loading">
1242
+ <h1>Loading...</h1>
101
1243
  </section>
1244
+ <!-- ...render based on `state.screen` as shown in the Login / Register example above -->
102
1245
  </template>
103
1246
  ```
104
1247
 
105
- #### Profile page example
1248
+ ##### Logout
1249
+
1250
+ **Client-managed sessions**:
1251
+
1252
+ Call this to clear the session and redirect to the Strivacity end-session endpoint. After that the user is redirected back to your app at `postLogoutRedirectUri`.
106
1253
 
107
1254
  ```vue
108
- <script setup>
109
- const { loading, isAuthenticated, accessToken, accessTokenExpired, accessTokenExpirationDate, idTokenClaims, refreshToken } = useStrivacity();
1255
+ <!-- app/pages/logout.vue -->
1256
+ <script setup lang="ts">
1257
+ const { logout } = useStrivacity();
1258
+
1259
+ onMounted(() => {
1260
+ void logout();
1261
+ });
110
1262
  </script>
111
1263
 
112
1264
  <template>
113
1265
  <section>
114
- <h1 v-if="loading">Loading...</h1>
115
- <dl v-else>
116
- <dt><strong>accessToken</strong></dt>
117
- <dd>
118
- <pre>{{ JSON.stringify(accessToken) }}</pre>
119
- </dd>
120
- <dt><strong>refreshToken</strong></dt>
121
- <dd>
122
- <pre>{{ JSON.stringify(refreshToken) }}</pre>
123
- </dd>
124
- <dt><strong>accessTokenExpired</strong></dt>
125
- <dd>
126
- <pre>{{ JSON.stringify(accessTokenExpired) }}</pre>
127
- </dd>
128
- <dt><strong>accessTokenExpirationDate</strong></dt>
129
- <dd>
130
- <pre>{{ accessTokenExpirationDate ? new Date(accessTokenExpirationDate * 1000).toLocaleString() : JSON.stringify(null) }}</pre>
131
- </dd>
132
- <dt><strong>claims</strong></dt>
133
- <dd>
134
- <pre>{{ JSON.stringify(idTokenClaims, null, 2) }}</pre>
135
- </dd>
136
- </dl>
1266
+ <h1>Logging out...</h1>
137
1267
  </section>
138
1268
  </template>
139
1269
  ```
140
1270
 
141
- #### Logout page example
1271
+ **Server-managed sessions**:
142
1272
 
143
- The `postLogoutRedirectUri` parameter is optional and specifies where users are redirected after logout. This URI must be configured in the Admin Console as an allowed post-logout redirect URI.
1273
+ With `serverSessionUri` configured, redirect to `/auth/logout` instead - the Server SDK clears the session and redirects to the IDP end-session endpoint:
144
1274
 
145
1275
  ```vue
146
- <script setup>
147
- import { onMounted } from 'vue';
1276
+ <!-- app/pages/logout.vue -->
1277
+ <script setup lang="ts">
1278
+ await navigateTo('/auth/logout', { external: true });
1279
+ </script>
1280
+ ```
148
1281
 
149
- const router = useRouter();
150
- const { isAuthenticated, logout } = useStrivacity();
1282
+ ##### Token management
151
1283
 
152
- onMounted(async () => {
153
- if (isAuthenticated.value) {
154
- await logout({ postLogoutRedirectUri: location.origin });
155
- } else {
156
- await router.push('/');
157
- }
158
- });
1284
+ **Client-managed sessions**:
1285
+
1286
+ Call these methods to manage the session and access token client-side.
1287
+
1288
+ ```vue
1289
+ <script setup lang="ts">
1290
+ const { loading, idTokenClaims, accessToken, refreshToken, refresh, revoke } = useStrivacity();
1291
+
1292
+ async function onRefresh() {
1293
+ // Refresh the access token using the refresh token
1294
+ await refresh();
1295
+ }
1296
+
1297
+ async function onRevoke() {
1298
+ // Revoke all tokens at the authorization server and clear the local session
1299
+ await revoke();
1300
+ }
159
1301
  </script>
160
1302
 
161
1303
  <template>
162
- <section>
163
- <h1>Logging out...</h1>
164
- </section>
1304
+ <div v-if="!loading">
1305
+ <button @click="onRefresh">Refresh</button>
1306
+ <button @click="onRevoke">Revoke</button>
1307
+ <pre>{{ JSON.stringify({ idTokenClaims, accessToken, refreshToken }, null, 2) }}</pre>
1308
+ </div>
165
1309
  </template>
166
1310
  ```
167
1311
 
168
- #### Component example
1312
+ **Server-managed sessions**:
1313
+
1314
+ With `serverSessionUri` configured, tokens are refreshed/revoked by the Server SDK - trigger it by navigating to the auth routes, then let it redirect back:
169
1315
 
170
1316
  ```vue
171
- <script setup>
172
- import { computed } from 'vue';
1317
+ <script setup lang="ts">
1318
+ function onRefresh() {
1319
+ // sdk.refreshSession() runs server-side, then redirects back to returnTo
1320
+ globalThis.location.href = '/auth/refresh?returnTo=/profile';
1321
+ }
173
1322
 
174
- const { isAuthenticated, idTokenClaims, login, logout } = useStrivacity();
175
- const name = computed(() => `${idTokenClaims.value?.given_name} ${idTokenClaims.value?.family_name}`);
1323
+ function onRevoke() {
1324
+ // sdk.revokeSession() runs server-side, then redirects to postLogoutRedirectUri
1325
+ globalThis.location.href = '/auth/revoke';
1326
+ }
176
1327
  </script>
177
1328
 
178
1329
  <template>
179
- <div v-if="isAuthenticated">
180
- <div>Welcome, {{ name }}!</div>
181
- <button @click="logout()">Logout</button>
182
- </div>
183
- <div v-else>
184
- <div>Not logged in</div>
185
- <button @click="login()">Log in</button>
1330
+ <div>
1331
+ <button @click="onRefresh">Refresh</button>
1332
+ <button @click="onRevoke">Revoke</button>
186
1333
  </div>
187
1334
  </template>
188
1335
  ```
189
1336
 
190
- ### Native mode
191
-
192
- In `native` mode the auto-imported `StyLoginRenderer` component renders the authentication UI inline using your custom widget components. You can define custom components for each input type; see [Example widgets](https://github.com/Strivacity/sdk-js/tree/main/apps/nuxt/app/components/widgets).
193
-
194
- The example widgets use SCSS for styling and Luxon for date handling:
1337
+ ---
195
1338
 
196
- ```bash
197
- npm install sass luxon
198
- npm install --save-dev @types/luxon
199
- ```
200
-
201
- ```js
202
- import CheckboxWidget from './checkbox.widget.vue';
203
- import DateWidget from './date.widget.vue';
204
- import InputWidget from './input.widget.vue';
205
- import LayoutWidget from './layout.widget.vue';
206
- import MultiSelectWidget from './multiselect.widget.vue';
207
- import PasscodeWidget from './passcode.widget.vue';
208
- import LoadingWidget from './loading.widget.vue';
209
- import PasswordWidget from './password.widget.vue';
210
- import PhoneWidget from './phone.widget.vue';
211
- import SelectWidget from './select.widget.vue';
212
- import StaticWidget from './static.widget.vue';
213
- import SubmitWidget from './submit.widget.vue';
214
-
215
- export const widgets = {
216
- checkbox: CheckboxWidget,
217
- date: DateWidget,
218
- input: InputWidget,
219
- layout: LayoutWidget,
220
- loading: LoadingWidget,
221
- passcode: PasscodeWidget,
222
- password: PasswordWidget,
223
- phone: PhoneWidget,
224
- select: SelectWidget,
225
- multiSelect: MultiSelectWidget,
226
- static: StaticWidget,
227
- submit: SubmitWidget,
228
- };
229
- ```
1339
+ ### Composables API
230
1340
 
231
- #### Login page example
1341
+ All composables below are auto-imported by the module - no import statement is needed in your `.vue` files.
232
1342
 
233
- The login page extracts `session_id` and optionally `language` from the URL on load, cleans up the URL, and passes them to the renderer. When a `session_id` is present the renderer calls `startSession(sessionId)` to resume the existing flow instead of starting a new one. When a `language` parameter is present it is passed to the renderer which uses it for the authentication UI and emits the resolved language back via `v-model:language`.
1343
+ #### useStrivacity
234
1344
 
235
- ```vue
236
- <script setup lang="ts">
237
- import { ref } from 'vue';
238
- import type { FallbackError, LoginFlowState } from '@strivacity/sdk-nuxt';
239
- import { widgets } from '~/components/widgets';
1345
+ The main composable for accessing the SDK instance and reactive session state.
240
1346
 
241
- const router = useRouter();
242
- const sessionId = ref<string | null>(null);
243
- const language = ref<string | null>(null);
1347
+ ```ts
1348
+ import type { RedirectFlow } from '@strivacity/sdk-nuxt';
244
1349
 
245
- if (window.location.search !== '') {
246
- const url = new URL(window.location.href);
247
- sessionId.value = url.searchParams.get('session_id');
1350
+ const ctx = useStrivacity<RedirectFlow>();
1351
+ ```
248
1352
 
249
- if (url.searchParams.has('language')) {
250
- language.value = url.searchParams.get('language');
251
- }
1353
+ ##### Returns
252
1354
 
253
- url.search = '';
254
- history.replaceState({}, '', url.toString());
1355
+ ```ts
1356
+ {
1357
+ // SDK instance (access any SDK method)
1358
+ readonly sdk: RedirectFlow | PopupFlow | EmbeddedFlow | NativeFlow;
1359
+
1360
+ // Reactive state
1361
+ loading: Ref<boolean>; // True during initialization
1362
+ language: Ref<string>; // Current BCP 47 language code
1363
+ isAuthenticated: Ref<boolean>; // True if user has valid session
1364
+ idTokenClaims: Ref<IdTokenClaims | null>; // Decoded ID token claims
1365
+ accessToken: Ref<string | null>; // Current access token
1366
+ refreshToken: Ref<string | null>; // Current refresh token
1367
+ accessTokenExpired: Ref<boolean>; // True once the access token has expired
1368
+ accessTokenExpirationDate: Ref<number | null>; // Access token expiration timestamp
1369
+
1370
+ // Methods (all are async)
1371
+ login(params?: LoginParams): Promise<void>; // Start login flow
1372
+ register(params?: LoginParams): Promise<void>; // Start registration flow
1373
+ handleCallback(url?: string): Promise<void>; // Handle OAuth callback
1374
+ logout(params?: LogoutParams): Promise<void>; // End session
1375
+ refresh(): Promise<void>; // Refresh access token
1376
+ revoke(): Promise<void>; // Revoke tokens
1377
+ entry(): Promise<EntryData>; // Handle external entry (embedded/native only)
255
1378
  }
1379
+ ```
256
1380
 
257
- const onLogin = async () => {
258
- await router.push('/profile');
259
- };
1381
+ #### useNativeLogin
260
1382
 
261
- const onFallback = (error: FallbackError) => {
262
- if (error.url) {
263
- window.location.href = error.url.toString();
264
- } else {
265
- alert(error);
266
- }
267
- };
1383
+ Composable for managing native login flow state. Only available in `native` mode.
268
1384
 
269
- const onError = (error: string) => {
270
- alert(error);
271
- };
1385
+ ```ts
1386
+ import type { NativeParams } from '@strivacity/sdk-nuxt';
272
1387
 
273
- const onGlobalMessage = (message: string) => {
274
- alert(message);
275
- };
1388
+ const ctx = useNativeLogin({
1389
+ params: { /* login params */ },
1390
+ onLogin: (session) => { /* handle login */ },
1391
+ onError: (error) => { /* handle error */ },
1392
+ // ... other callbacks
1393
+ });
1394
+ ```
276
1395
 
277
- const onBlockReady = ({ previousState, state }: { previousState: LoginFlowState; state: LoginFlowState }) => {
278
- console.log('previousState', previousState);
279
- console.log('state', state);
280
- };
281
- </script>
1396
+ ##### Options
282
1397
 
283
- <template>
284
- <StyLoginRenderer
285
- v-model:language="language"
286
- :widgets="widgets"
287
- :session-id="sessionId"
288
- @fallback="onFallback"
289
- @login="onLogin"
290
- @error="onError"
291
- @global-message="onGlobalMessage"
292
- @block-ready="onBlockReady"
293
- />
294
- </template>
1398
+ ```ts
1399
+ {
1400
+ params?: NativeParams; // Initial flow parameters
1401
+ onLogin?: (session: SessionData) => void | Promise<void>; // Called on successful login
1402
+ onClose?: () => void; // Called when user closes the flow
1403
+ onError?: (error: unknown) => void; // Called on error
1404
+ onFallback?: (error: FallbackError) => void; // Called when fallback needed
1405
+ onGlobalMessage?: (message: NativeFlowMessage) => void; // Called for global messages
1406
+ }
295
1407
  ```
296
1408
 
297
- #### Callback page example
1409
+ ##### Returns
298
1410
 
299
- When a `session_id` is present in the URL the native flow is resumed by forwarding it to the login page. Otherwise the standard `handleCallback()` path is used:
1411
+ ```ts
1412
+ {
1413
+ // Reactive state
1414
+ loading: Ref<boolean>; // True while fetching next screen
1415
+ state: Ref<Partial<NativeFlowState>>; // Current flow state (screen, forms, layout, etc.)
1416
+ forms: Ref<Record<string, Record<string, unknown>>>; // Form data by form ID
1417
+ messages: Ref<Record<string, Record<string, NativeFlowMessage>>>; // Validation messages
1418
+
1419
+ // Methods
1420
+ submitForm(formId: string, customBody?: Record<string, unknown>): Promise<void>; // Submit a form and advance to next screen
1421
+ setFormValue(formId: string, widgetId: string, value: unknown): void; // Update a single field value before submission
1422
+ setMessage(formId: string, widgetId: string, value: NativeFlowMessage): void; // Set a validation/info message on a widget
1423
+ triggerFallback(message?: string): void; // Manually trigger fallback to hosted journey
1424
+ triggerClose(): void; // Signal that the login flow was closed by the user
1425
+ }
1426
+ ```
300
1427
 
301
- ```vue
302
- <script setup>
303
- import { onMounted, computed } from 'vue';
1428
+ If you split widget rendering into sub-components, `useNativeLogin()` also `provide()`s its returned value under the hood - read it in any descendant with `useNativeLoginContext()` instead of threading props through every level:
304
1429
 
305
- const query = computed(() => Object.fromEntries(new URLSearchParams(window.location.search)));
306
- const router = useRouter();
307
- const { handleCallback } = useStrivacity();
1430
+ ```ts
1431
+ const { loading, forms, messages, setFormValue, submitForm } = useNativeLoginContext();
1432
+ ```
308
1433
 
309
- onMounted(async () => {
310
- const url = new URL(location.href);
311
- const sessionId = url.searchParams.get('session_id');
312
-
313
- if (sessionId) {
314
- await router.push(`/login?session_id=${sessionId}`);
315
- } else {
316
- try {
317
- await handleCallback();
318
- await router.push('/profile');
319
- } catch (error) {
320
- console.error('Error during callback handling:', error);
321
- }
322
- }
323
- });
324
- </script>
1434
+ ---
325
1435
 
326
- <template>
327
- <section v-if="query.error">
328
- <h1>Error in authentication</h1>
329
- <div>
330
- <h4>{{ query.error }}</h4>
331
- <p>{{ query.error_description }}</p>
332
- </div>
333
- </section>
334
- <section v-else>
335
- <h1>Logging in...</h1>
336
- </section>
337
- </template>
338
- ```
1436
+ ## Server SDK
339
1437
 
340
- #### Entry page example
1438
+ This is the same backend-for-frontend ([BFF](../../README.md#bff)) server implementation as the [core Server SDK](../sdk-core/README.md#server-sdk), pre-wired as a Nitro plugin: `defineNuxtModule` builds a `BaseServerSDK<H3Event>` once per request and stores it on `event.context.strivacity.sdk`, and a default encrypted-cookie storage is used unless you configure something else.
341
1439
 
342
- The entry page processes flows started by an external process (e.g. password reset) by calling `entry()` to extract the necessary parameters to resume the flow and forwarding them to the callback page:
1440
+ ### Setup
343
1441
 
344
- ```vue
345
- <script setup>
346
- import { onMounted } from 'vue';
1442
+ There's no server file to create or mount - the module registers everything (the plugin, the auth routes, the session-hydration middleware) automatically from the single `strivacity: {...}` key in `nuxt.config.ts`. See [Quick start](#quick-start) for the full configuration example, including `secret` (required unless you provide a custom `storage`).
347
1443
 
348
- const router = useRouter();
349
- const { entry } = useStrivacity();
1444
+ ### Accessing the session server-side
350
1445
 
351
- onMounted(async () => {
352
- try {
353
- const data = await entry();
1446
+ Call the auto-imported server composable `useStrivacity(event)` to read the current session (or invoke any other Server SDK method) from a server route, without going through the client SDK:
354
1447
 
355
- if (data && Object.keys(data).length > 0) {
356
- await router.push(`/callback?${new URLSearchParams(data).toString()}`);
357
- } else {
358
- await router.push('/');
359
- }
360
- } catch (error) {
361
- console.error('Entry failed:', error);
362
- await router.push('/');
1448
+ ```ts
1449
+ // server/api/profile.get.ts
1450
+ export default defineEventHandler(async (event) => {
1451
+ const sdk = useStrivacity(event);
1452
+ const session = await sdk.getSession();
1453
+
1454
+ if (!session) {
1455
+ throw createError({ statusCode: 401 });
363
1456
  }
1457
+
1458
+ return { idTokenClaims: session.idTokenClaims };
364
1459
  });
365
- </script>
366
1460
  ```
367
1461
 
368
- #### Profile page example
1462
+ > `useStrivacity(event)` throws if called on the client or without an `H3Event` - it's only available inside server routes, server plugins, and Nitro middleware.
369
1463
 
370
- Same as the profile page example in redirect/popup mode.
1464
+ <a id="server-storages"></a>
1465
+ ### Storages
371
1466
 
372
- #### Logout page example
1467
+ By default the Server SDK stores tokens encrypted in http-only cookies and login state in a global in-memory `Map`. Custom storages (and custom `logging`/`httpClient` implementations) aren't passed inline in `nuxt.config.ts` - they're referenced by **file path**, because `nuxt.config.ts` is evaluated in a context that can't hold live server-only code. Point `storageFactoryPath`/`stateStorageFactoryPath` at a module that default-exports a factory function.
373
1468
 
374
- Same as the logout page example in redirect/popup mode.
1469
+ #### Built-in session storages
375
1470
 
376
- ### Embedded mode
377
-
378
- In `embedded` mode the `<sty-login>` web component (loaded via `bundle.js` from the cluster) handles rendering. Import the bundle in a Nuxt plugin to register the Strivacity web components:
1471
+ - **`createEncryptedCookieStorage(secret, options?)`** - default storage that keeps the session encrypted in an http-only cookie.
1472
+ - **`createSessionIdCookieStorage(storage, options?)`** - puts only a small, random session-id cookie on the client and keeps the actual session payload in the `storage` you provide. This supports back-channel logout out of the box.
379
1473
 
380
1474
  ```ts
381
- // plugins/strivacity-bundle.client.ts
382
- export default defineNuxtPlugin(() => {
383
- const config = useRuntimeConfig();
384
- void import(`${config.public.strivacity.issuer}/assets/components/bundle.js`);
385
- });
1475
+ // server/storage.ts
1476
+ import { createSessionIdCookieStorage, createServerMemoryStorage } from '#imports';
1477
+
1478
+ export default function createStorageFactory() {
1479
+ return createSessionIdCookieStorage(
1480
+ createServerMemoryStorage(),
1481
+ {
1482
+ // maxAge: 30 * 24 * 60 * 60 // Without maxAge this is a browser-session cookie that gets cleared when the browser closes
1483
+ },
1484
+ );
1485
+ }
386
1486
  ```
387
1487
 
388
1488
  ```ts
@@ -390,216 +1490,287 @@ export default defineNuxtPlugin(() => {
390
1490
  export default defineNuxtConfig({
391
1491
  modules: ['@strivacity/sdk-nuxt'],
392
1492
  strivacity: {
393
- mode: 'embedded',
394
- issuer: 'https://<YOUR_DOMAIN>',
395
- scopes: ['openid', 'profile'],
396
- clientId: '<YOUR_CLIENT_ID>',
397
- redirectUri: '<YOUR_REDIRECT_URI>',
1493
+ // ...other options
1494
+ storageFactoryPath: '~/server/storage', // default-exports the factory shown above
398
1495
  },
399
1496
  });
400
1497
  ```
401
1498
 
402
- ## Logging
1499
+ #### Custom storage
403
1500
 
404
- The SDK supports optional logging to help you debug authentication flows and monitor SDK behavior. You can enable the built-in console logger or provide your own custom logger implementation.
405
-
406
- ### Using the Default Logger
407
-
408
- Enable the default console logger by adding the `logging` option in `nuxt.config.ts`:
1501
+ For example you can use Redis via [unstorage](https://npmjs.com/package/unstorage):
409
1502
 
410
1503
  ```ts
411
- import { DefaultLogging } from '@strivacity/sdk-nuxt';
1504
+ // server/storage.ts
1505
+ import { createStorage } from 'unstorage';
1506
+ import redisDriver from 'unstorage/drivers/redis';
1507
+ import type { NuxtServerStorage } from '@strivacity/sdk-nuxt';
1508
+
1509
+ const unstorageInstance = createStorage({ driver: redisDriver({ url: process.env.REDIS_URL }) });
1510
+
1511
+ export default function createStorageFactory(): NuxtServerStorage {
1512
+ return {
1513
+ async get(key) {
1514
+ return unstorageInstance.getItem<string>(key);
1515
+ },
1516
+ async set(key, value) {
1517
+ await unstorageInstance.setItem(key, value);
1518
+ },
1519
+ async delete(key) {
1520
+ await unstorageInstance.removeItem(key);
1521
+ },
1522
+ // Required for back-channel logout support - see below.
1523
+ // Scans all stored sessions and removes those matching the logout token's sid or sub claim.
1524
+ async deleteByLogoutToken(logoutToken) {
1525
+ const keys = await unstorageInstance.getKeys();
1526
+ await Promise.all(
1527
+ keys.map(async (key) => {
1528
+ const raw = await unstorageInstance.getItem<string>(key);
1529
+ if (!raw) return;
1530
+ const session = JSON.parse(raw);
1531
+ if ((logoutToken.sid && session.sid === logoutToken.sid) || (logoutToken.sub && session.sub === logoutToken.sub)) {
1532
+ await unstorageInstance.removeItem(key);
1533
+ }
1534
+ }),
1535
+ );
1536
+ },
1537
+ };
1538
+ }
1539
+ ```
412
1540
 
1541
+ ```ts
1542
+ // nuxt.config.ts
413
1543
  export default defineNuxtConfig({
414
1544
  modules: ['@strivacity/sdk-nuxt'],
415
1545
  strivacity: {
416
- mode: 'redirect',
417
- issuer: 'https://<YOUR_DOMAIN>',
418
- scopes: ['openid', 'profile'],
419
- clientId: '<YOUR_CLIENT_ID>',
420
- redirectUri: '<YOUR_REDIRECT_URI>',
421
- logging: DefaultLogging,
1546
+ // ...other options
1547
+ storageFactoryPath: '~/server/storage', // default-exports the factory shown above
422
1548
  },
423
1549
  });
424
1550
  ```
425
1551
 
426
- ### Creating a Custom Logger
1552
+ > For more details on the storage interfaces, see the core SDK's [Custom storage](../sdk-core/README.md#server-storages) section.
427
1553
 
428
- Implement the `SDKLogging` interface and pass your class to the `logging` option:
1554
+ ### Back-channel logout
429
1555
 
430
- ```typescript
431
- import type { SDKLogging } from '@strivacity/sdk-nuxt';
1556
+ OIDC back-channel logout lets the authorization server terminate sessions server-to-server, without involving the browser. When the IDP sends a logout event (e.g. an admin terminates a session, or the user logs out from a different device), it POSTs a signed `logout_token` JWT to `/auth/backchannel-logout` - already registered by the module - which routes it to `sdk.handleBackChannelLogout(event)`.
432
1557
 
433
- export class MyLogger implements SDKLogging {
434
- xEventId?: string;
1558
+ The handler verifies the token's signature against the IDP's JWKS, validates the `iss`, `aud`, `iat` (freshness), and `jti` (replay protection) claims, requires the `http://schemas.openid.net/event/backchannel-logout` event and a `sid` or `sub` claim, then calls `storage.deleteByLogoutToken({ sid?, sub? })` to remove the matching session(s). It responds `200` on success, `400` for an invalid or malformed `logout_token`, and `501` if the configured storage doesn't implement `deleteByLogoutToken`.
435
1559
 
436
- debug(message: string): void {
437
- console.debug(this.xEventId ? `[${this.xEventId}] ${message}` : message);
438
- }
1560
+ > **The default encrypted-cookie storage does not support back-channel logout** because each cookie is bound to a single browser session - there is no server-side index to look up by `sid` or `sub`. To support back-channel logout, use [`createSessionIdCookieStorage`](#server-storages) with a `storage` that implements `deleteByLogoutToken` (e.g. `createServerMemoryStorage()` for local testing), or a fully custom server storage as shown in the [Custom storage](#server-storages) example above.
439
1561
 
440
- info(message: string): void {
441
- console.info(this.xEventId ? `[${this.xEventId}] ${message}` : message);
442
- }
1562
+ Configure the **Back-channel logout URI** in your Strivacity application settings to:
443
1563
 
444
- warn(message: string): void {
445
- console.warn(this.xEventId ? `[${this.xEventId}] ${message}` : message);
446
- }
1564
+ ```
1565
+ https://your-app.example.com/auth/backchannel-logout
1566
+ ```
447
1567
 
448
- error(message: string, error: Error): void {
449
- console.error(this.xEventId ? `[${this.xEventId}] ${message}` : message, error);
450
- }
1568
+ For a complete explanation of the handshake and validation performed, see the core SDK's [Back-channel logout](../sdk-core/README.md#server-backchannel-logout) documentation.
1569
+
1570
+ ### Server SDK API reference
1571
+
1572
+ `NuxtServerSDK` (`event.context.strivacity.sdk`, or the return value of `useStrivacity(event)`):
1573
+
1574
+ ```ts
1575
+ {
1576
+ options: NuxtServerSDKOptions; // resolved server SDK configuration
1577
+
1578
+ // Session management
1579
+ getSession(): Promise<SessionData | null>; // read the current session
1580
+ updateSession(session): Promise<void>; // persist new session data
1581
+ refreshSession(): Promise<SessionData>; // refresh tokens using the refresh token
1582
+ revokeSession(): Promise<void>; // revoke tokens and clear the session
1583
+ getEntrySession(entryUrl): Promise<Record<string, string>>; // resolve an externally-initiated (embedded/native) entry URL
1584
+ completeLogin(params): Promise<SessionData>; // exchange an authorization code for tokens
1585
+ logout(postLogoutRedirectUri): Promise<URL>; // clear the session, returns the IDP end-session URL
1586
+
1587
+ // Route handlers - each returns a Response; `handler` dispatches to the one matching the request path
1588
+ handleLogin(): Promise<Response>;
1589
+ handleRegister(): Promise<Response>;
1590
+ handleCallback(): Promise<Response>;
1591
+ handleRefresh(): Promise<Response>;
1592
+ handleRevoke(): Promise<Response>;
1593
+ handleEntry(): Promise<Response>;
1594
+ handleLogout(): Promise<Response>;
1595
+ handleBackChannelLogout(): Promise<Response>;
1596
+ handler(): Promise<Response>;
451
1597
  }
452
1598
  ```
453
1599
 
454
- The `SDKLogging` interface requires `debug`, `info`, `warn`, and `error` methods. The optional `xEventId` property, when set by the SDK, provides a correlation ID to trace related log messages across the authentication flow.
1600
+ All methods already close over the current `H3Event` - there's no `event` parameter to pass at the call site.
455
1601
 
456
- ## HTTP Client
1602
+ ### Server configuration reference
457
1603
 
458
- The SDK uses a built-in `fetch`-based HTTP client for all requests. You can replace it with your own implementation by extending `SDKHttpClient` and passing your class via the `httpClient` option. This is useful when you need to attach custom headers (e.g. `x-sty-app-id`) to every outgoing request or route traffic through a proxy.
1604
+ The Server SDK accepts the same configuration as the client SDK (see [Configuration reference](#configuration-reference)), plus:
459
1605
 
460
- ### Adding custom headers to every request
1606
+ | Option | Type | Required | Default | Description |
1607
+ | ------ | ---- | -------- | ------- | ----------- |
1608
+ | `secret` | `string` | Only if using default storage | - | Encryption key (32+ random characters) for the http-only cookie session storage |
1609
+ | `storageFactoryPath` | `string` | No | Encrypted cookie storage | Path to a module default-exporting a `NuxtServerStorage` factory; see [Storages](#server-storages) |
1610
+ | `stateStorageFactoryPath` | `string` | No | In-memory `Map` | Path to a module default-exporting an `SDKStorage` factory for the OAuth2 state parameter |
1611
+ | `loggingFactoryPath` | `string` | No | - | Path to a module default-exporting an `SDKLogging` factory |
1612
+ | `httpClientFactoryPath` | `string` | No | - | Path to a module default-exporting an `SDKHttpClient` factory |
1613
+ | `authUrlPrefix` | `string` | No | `'/auth'` | URL prefix under which the module registers the auth routes |
1614
+ | `serverSessionUri` | `string \| null` | No | `` `${authUrlPrefix}/login` `` | Enables server-managed sessions when set (the default); set to `null` to use client-managed sessions instead |
1615
+ | `postLoginRedirectUri` | `string` | No | - | Default redirect after login when no `?returnTo=` is given |
1616
+ | `postLogoutRedirectUri` | `string` | No | - | Default redirect after logout |
1617
+ | `cookieMaxAge` | `number` | No | `2592000` (30 days) | Max age of the session cookie in seconds |
461
1618
 
462
- ```typescript
463
- // nuxt.config.ts
464
- import { SDKHttpClient, type HttpClientResponse } from '@strivacity/sdk-nuxt';
465
-
466
- class CustomHttpClient extends SDKHttpClient {
467
- async request<T>(url: string, options?: RequestInit): Promise<HttpClientResponse<T>> {
468
- const mergedOptions: RequestInit = {
469
- ...options,
470
- headers: {
471
- 'x-sty-app-id': 'my-app',
472
- ...(options?.headers as Record<string, string>),
473
- },
474
- };
475
-
476
- const response = await fetch(url, mergedOptions);
477
-
478
- return {
479
- headers: response.headers,
480
- ok: response.ok,
481
- status: response.status,
482
- statusText: response.statusText,
483
- url: response.url,
484
- json: async () => (await response.json()) as T,
485
- text: async () => await response.text(),
486
- };
487
- }
488
- }
1619
+ ---
489
1620
 
490
- export default defineNuxtConfig({
491
- modules: ['@strivacity/sdk-nuxt'],
492
- strivacity: {
493
- // ...other options
494
- httpClient: CustomHttpClient,
495
- },
496
- });
497
- ```
1621
+ ### Route guards
498
1622
 
499
- Any header you add inside `request()` is automatically included in every SDK request
1623
+ There's no client-side route guard composable in this SDK. Instead, add an opt-in Nuxt route middleware and apply it per-page:
500
1624
 
501
- ### CORS configuration
1625
+ ```ts
1626
+ // app/middleware/auth.ts
1627
+ export default defineNuxtRouteMiddleware(async (to) => {
1628
+ const sdk = useStrivacity();
1629
+ const authenticated = sdk.options.serverSessionUri ? !!useSession().value : await sdk.isAuthenticated;
502
1630
 
503
- For custom request headers to reach the Strivacity cluster, the cluster must be configured to explicitly allow them. Add the header name(s) to the **Access-Control-Allow-Headers** list in the cluster settings. Without this, browsers will block the preflight `OPTIONS` request and the SDK call will fail with a CORS error.
1631
+ if (!authenticated) {
1632
+ const returnToCookie = useCookie('sty.returnTo');
1633
+ returnToCookie.value = to.fullPath;
504
1634
 
505
- ```
506
- Access-Control-Allow-Headers: x-sty-app-id, <any other custom headers>
1635
+ return navigateTo('/login');
1636
+ }
1637
+ });
507
1638
  ```
508
1639
 
509
- ## API Documentation
1640
+ ```vue
1641
+ <!-- app/pages/profile.vue -->
1642
+ <script setup lang="ts">
1643
+ definePageMeta({ middleware: ['auth'] });
1644
+ </script>
1645
+ ```
510
1646
 
511
- ### `useStrivacity` composable
1647
+ This is separate from the global session-hydration middleware the module registers automatically (see [Quick start](#quick-start)) - that one always runs and only seeds `useSession()`, it never redirects. The guard above is what actually protects a page, and you choose which pages need it.
512
1648
 
513
- ```typescript
514
- useStrivacity<T extends PopupContext | RedirectContext | NativeContext>(): T;
515
- ```
1649
+ ---
516
1650
 
517
- The composable returns a different context type depending on the `mode` configured in `nuxt.config.ts`.
1651
+ ## Shared features
518
1652
 
519
- **Shared properties (all modes)**
1653
+ The Nuxt SDK is built on top of the core SDK and supports all its features, on both the client and server:
520
1654
 
521
- - **`sdk: RedirectFlow | PopupFlow | NativeFlow`**: The underlying SDK flow instance.
522
- - **`loading: Ref<boolean>`**: `true` while the session is being initialized.
523
- - **`options: SDKOptions`**: The configured SDK options.
524
- - **`isAuthenticated: Ref<boolean>`**: `true` when the user has a valid session.
525
- - **`idTokenClaims: Ref<IdTokenClaims | null>`**: Claims from the ID token, or `null` if not authenticated.
526
- - **`accessToken: Ref<string | null>`**: The current access token.
527
- - **`refreshToken: Ref<string | null>`**: The current refresh token.
528
- - **`accessTokenExpired: Ref<boolean>`**: `true` when the access token has expired.
529
- - **`accessTokenExpirationDate: Ref<number | null>`**: Expiration timestamp (Unix seconds) of the access token.
1655
+ - **[Storages](../sdk-core/README.md#storages)** - localStorage, sessionStorage, IndexedDB, Cache API, Memory, Worker (client), encrypted cookies, in-memory (server)
1656
+ - **[SDK events](../sdk-core/README.md#sdk-events)** - Subscribe to authentication lifecycle events
1657
+ - **[Logging](../sdk-core/README.md#logging)** - Built-in and custom logger support
1658
+ - **[HTTP client](../sdk-core/README.md#http-client)** - Custom HTTP client integration
1659
+ - **[Error handling](../sdk-core/README.md#error-handling)** - Typed error classes for different failure scenarios
1660
+ - **[Utility functions](../sdk-core/README.md#utility-functions)** - Base64URL, JWT decoding, encryption, etc.
1661
+ - **[Caching](../sdk-core/README.md#caching)** - OIDC metadata and JWKS caching
530
1662
 
531
1663
  ---
532
1664
 
533
- **Type: `RedirectContext`**
1665
+ ## Configuration reference
534
1666
 
535
- - **`login(options?: LoginOptions): Promise<void>`**: Initiates login by redirecting to the identity provider.
536
- - **`register(options?: RegisterOptions): Promise<void>`**: Initiates registration using a redirect flow.
537
- - **`refresh(): Promise<void>`**: Refreshes the user's session.
538
- - **`revoke(): Promise<void>`**: Revokes the current session tokens.
539
- - **`logout(options?: LogoutOptions): Promise<void>`**: Logs the user out via redirect.
540
- - **`handleCallback(url?: string): Promise<void>`**: Processes the authorization callback after redirect.
541
- - **`entry(): Promise<Record<string, string>>`**: Processes an externally-initiated flow URL and returns the parameters needed to resume the flow.
1667
+ The client SDK accepts the same configuration as the core SDK - see the [core SDK configuration reference](../sdk-core/README.md#configuration-reference). See [Server configuration reference](#server-configuration-reference) above for the additional server-specific options.
542
1668
 
543
1669
  ---
544
1670
 
545
- **Type: `PopupContext`**
1671
+ ## Migration guide
546
1672
 
547
- - **`login(options?: LoginOptions): Promise<void>`**: Initiates login using a popup window.
548
- - **`register(options?: RegisterOptions): Promise<void>`**: Initiates registration using a popup.
549
- - **`refresh(): Promise<void>`**: Refreshes the user's session.
550
- - **`revoke(): Promise<void>`**: Revokes the current session tokens.
551
- - **`logout(options?: LogoutOptions): Promise<void>`**: Logs the user out via popup.
552
- - **`handleCallback(url?: string): Promise<void>`**: Processes the authorization callback.
553
- - **`entry(): Promise<Record<string, string>>`**: Processes an externally-initiated flow URL.
1673
+ ### Migrating to v4.0
554
1674
 
555
- ---
1675
+ v4 replaces the SDK's class-based flow architecture with function-based architecture, and unifies server-managed (BFF) session handling onto a shared Server SDK. `useStrivacity()`, `useNativeLogin()`, and the `strivacity: {...}` module configuration are unchanged - only apps that used `mode: 'custom'` or drove `native` mode through the old `NativeFlowHandler` need to update their code.
556
1676
 
557
- **Type: `NativeContext`**
1677
+ #### Class-based flows replaced by functions
558
1678
 
559
- - **`login(options?: LoginOptions): Promise<NativeFlowHandler>`**: Initiates login using the native flow.
560
- - **`register(options?: RegisterOptions): Promise<NativeFlowHandler>`**: Initiates registration using the native flow.
561
- - **`refresh(): Promise<void>`**: Refreshes the user's session.
562
- - **`revoke(): Promise<void>`**: Revokes the current session tokens.
563
- - **`logout(options?: LogoutOptions): Promise<void>`**: Logs the user out via redirect.
564
- - **`handleCallback(url?: string): Promise<void>`**: Processes the authorization callback.
565
- - **`entry(): Promise<Record<string, string>>`**: Processes an externally-initiated flow URL.
1679
+ In v3, flows were classes (`RedirectFlow`, `PopupFlow`, `NativeFlow`, `EmbeddedFlow`), and the only way to customize behavior beyond the built-in modes - for example, to proxy authentication through your own backend in a bespoke way - was `mode: 'custom'` with a `customFlow` class that extended one of them and override its methods:
566
1680
 
567
- ---
1681
+ ```ts
1682
+ // v3
1683
+ import { NativeFlow } from '@strivacity/sdk-core/flows/NativeFlow';
568
1684
 
569
- ### `StyLoginRenderer` component
1685
+ export class CustomNativeFlow extends NativeFlow {
1686
+ override async refresh(): Promise<void> {
1687
+ // ...
1688
+ }
1689
+ }
1690
+ ```
570
1691
 
571
- Auto-imported in `native` mode to render the authentication UI with your own widget components.
1692
+ v4 removes `mode: 'custom'`, the `customFlow` option, and the flow classes entirely. In their place, `createBaseFlow` (from `@strivacity/sdk-core/flows/base`) is a factory function that returns a plain object of methods closing over shared state - build your own flow by composing it, without extending anything:
572
1693
 
573
- **Props**
1694
+ ```ts
1695
+ // v4
1696
+ import { createBaseFlow } from '@strivacity/sdk-core/flows/base';
1697
+ import { getDefaultFlowState, getSDKOptions } from '@strivacity/sdk-core/utils';
1698
+ import type { SDKInitConfig, SDKOptions } from '@strivacity/sdk-core/types';
1699
+
1700
+ export function createCustomFlow(initConfig: SDKInitConfig) {
1701
+ const state = getDefaultFlowState();
1702
+ const options = getSDKOptions<SDKOptions>(state, initConfig);
1703
+ const base = createBaseFlow(state, options);
1704
+
1705
+ async function refresh(): Promise<void> {
1706
+ // ...
1707
+ }
574
1708
 
575
- - **`params?: NativeParams`**: Additional parameters for the native login flow.
576
- - **`widgets?: PartialRecord<WidgetType, Vue.Component>`**: Custom Vue components for each widget type used in the flow.
577
- - **`sessionId?: string | null`**: Session ID for resuming an existing authentication session.
578
- - **`language?: string | null`**: Language tag (e.g. `"en-US"`) for the authentication UI. Defaults to `navigator.language`. Supports two-way binding via `v-model:language` — after the session starts the component emits the resolved language back to the parent. See the [Translations](https://docs.strivacity.com/docs/translations) page to learn about language precedence implemented by the product.
1709
+ return { ...base, refresh };
1710
+ }
1711
+ ```
579
1712
 
580
- **Events**
1713
+ This is a low-level `@strivacity/sdk-core` primitive - it's used the same way no matter which framework package you build on top of it. Wire it up by adding `factory: createCustomFlow` to the `strivacity: {...}` key in `nuxt.config.ts` - see [Custom flow](../sdk-core/README.md#custom-flow) in the core SDK README for the full pattern and usage example.
581
1714
 
582
- - **`@login`**: Emitted on successful authentication. Receives `IdTokenClaims | null`.
583
- - **`@fallback`**: Emitted when the native flow needs to fall back to redirect. Receives `FallbackError` with a fallback URL.
584
- - **`@error`**: Emitted when an error occurs during authentication.
585
- - **`@global-message`**: Emitted when the flow wants to display a global message (e.g. account lockout warning).
586
- - **`@block-ready`**: Emitted on flow state transitions. Receives `{ previousState: LoginFlowState; state: LoginFlowState }`. Useful for analytics and custom logging.
587
- - **`@update:language`**: Emitted after the session starts with the resolved language string. Used automatically by `v-model:language`.
1715
+ #### Server-managed sessions now share a common Server SDK
588
1716
 
589
- ## Vulnerability Reporting
1717
+ Server-managed sessions in `@strivacity/sdk-nuxt` are still configured entirely through the `strivacity: {...}` key in `nuxt.config.ts` - add `serverSessionUri` (and `secret`) and the module registers the auth routes and session-hydration middleware for you, with no server file or manual router mounting needed:
590
1718
 
591
- The [Guidelines for responsible disclosure](https://www.strivacity.com/report-a-security-issue) details the procedure for disclosing security issues. Please do not report security vulnerabilities on the public issue tracker.
1719
+ ```ts
1720
+ // nuxt.config.ts
1721
+ export default defineNuxtConfig({
1722
+ modules: ['@strivacity/sdk-nuxt'],
1723
+ strivacity: {
1724
+ // ...
1725
+ serverSessionUri: '/auth/login', // omit this line entirely for client-managed sessions
1726
+ secret: process.env.SECRET,
1727
+ },
1728
+ });
1729
+ ```
592
1730
 
593
- ## License
1731
+ What changed under the hood in v4: this is now built on the same shared Server SDK (`createBaseServerSDK` from `@strivacity/sdk-core/server`) used by every other framework package, instead of a bespoke Nuxt-only implementation - PKCE, state, and session storage now work identically across all frameworks.
594
1732
 
595
- @strivacity/sdk-nuxt is available under the MIT License. See the [LICENSE](https://github.com/Strivacity/sdk-js/blob/main/LICENSE) file for more info.
1733
+ #### Native mode: no more `NativeFlowHandler`
596
1734
 
597
- ## Contributing
1735
+ In v3, `native` mode's `login()`/`register()` returned a separate `NativeFlowHandler` instance, and the flow was driven through that handler:
598
1736
 
599
- Please see our [contributing guide](https://github.com/Strivacity/sdk-js/blob/main/CONTRIBUTING.md).
1737
+ ```ts
1738
+ // v3
1739
+ const handler = await sdk.login();
1740
+ const state = await handler.startSession(sessionId);
1741
+ const nextState = await handler.submitForm('formId', { identifier: 'user@example.com' });
1742
+ await handler.finalizeSession(nextState.finalizeUrl);
1743
+ ```
1744
+
1745
+ v4 moves `startSession()`, `submitForm()`, and `finalizeSession()` directly onto the flow itself - in `@strivacity/sdk-nuxt` this is wrapped for you by the auto-imported [`useNativeLogin()`](#usenativelogin) composable:
1746
+
1747
+ ```ts
1748
+ // v4
1749
+ const { state, forms, messages, submitForm } = useNativeLogin({
1750
+ params: { sessionId },
1751
+ });
1752
+
1753
+ await submitForm('formId');
1754
+ ```
1755
+
1756
+ Update any code that calls `login()`/`register()` and drives the returned handler in `native` mode to use `useNativeLogin()` instead.
600
1757
 
601
1758
  ## Migrating to v3.0
602
1759
 
603
1760
  ### Entry API Major Changes
604
1761
 
605
1762
  Strivacity SDK's `entry()` API now returns a structured object instead of a plain string. Check the example above in the usage section for more details.
1763
+
1764
+ ---
1765
+
1766
+ ## Vulnerability Reporting
1767
+
1768
+ The [Guidelines for responsible disclosure](https://www.strivacity.com/report-a-security-issue) details the procedure for disclosing security issues. Please do not report security vulnerabilities on the public issue tracker.
1769
+
1770
+ ## License
1771
+
1772
+ This package is available under the MIT License. See the [LICENSE](https://github.com/Strivacity/sdk-js/blob/main/LICENSE) file for more info.
1773
+
1774
+ ## Contributing
1775
+
1776
+ Please see our [contributing guide](https://github.com/Strivacity/sdk-js/blob/main/CONTRIBUTING.md).