@strivacity/sdk-vue 3.0.3 → 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 (61) hide show
  1. package/CHANGELOG.md +14 -18
  2. package/README.md +1402 -419
  3. package/dist/composables.cjs +2 -2
  4. package/dist/composables.cjs.map +1 -1
  5. package/dist/composables.d.ts +23 -8
  6. package/dist/composables.mjs +2 -2
  7. package/dist/composables.mjs.map +1 -1
  8. package/dist/errors.cjs +1 -0
  9. package/dist/errors.d.ts +1 -0
  10. package/dist/errors.mjs +1 -0
  11. package/dist/index.cjs +1 -2
  12. package/dist/index.d.ts +9 -31
  13. package/dist/index.mjs +1 -2
  14. package/dist/plugin.cjs +2 -0
  15. package/dist/plugin.cjs.map +1 -0
  16. package/dist/plugin.d.ts +8 -0
  17. package/dist/plugin.mjs +2 -0
  18. package/dist/plugin.mjs.map +1 -0
  19. package/dist/storages.cjs +1 -0
  20. package/dist/storages.d.ts +1 -0
  21. package/dist/storages.mjs +1 -0
  22. package/dist/types.cjs +1 -2
  23. package/dist/types.d.ts +106 -118
  24. package/dist/types.mjs +1 -2
  25. package/dist/utils.cjs +1 -0
  26. package/dist/utils.d.ts +1 -0
  27. package/dist/utils.mjs +1 -0
  28. package/eslint.config.mjs +3 -0
  29. package/package.json +45 -8
  30. package/project.json +46 -0
  31. package/src/composables.ts +219 -0
  32. package/src/env.d.ts +8 -0
  33. package/src/errors.ts +1 -0
  34. package/src/index.ts +9 -0
  35. package/src/plugin.ts +89 -0
  36. package/src/storages.ts +1 -0
  37. package/src/types.ts +220 -0
  38. package/src/utils.ts +1 -0
  39. package/testing/tests/composables.spec.ts +328 -0
  40. package/testing/tests/errors.spec.ts +10 -0
  41. package/testing/tests/index.spec.ts +105 -0
  42. package/testing/tests/plugin.spec.ts +129 -0
  43. package/testing/tests/storages.spec.ts +10 -0
  44. package/testing/tests/utils.spec.ts +10 -0
  45. package/testing/utils/common.ts +53 -0
  46. package/tsconfig.app.json +4 -0
  47. package/tsconfig.json +3 -0
  48. package/vite.config.mts +53 -0
  49. package/dist/assets/login-renderer.vue_vue_type_script_setup_true_lang.cjs +0 -2
  50. package/dist/assets/login-renderer.vue_vue_type_script_setup_true_lang.cjs.map +0 -1
  51. package/dist/assets/login-renderer.vue_vue_type_script_setup_true_lang.mjs +0 -2
  52. package/dist/assets/login-renderer.vue_vue_type_script_setup_true_lang.mjs.map +0 -1
  53. package/dist/index.cjs.map +0 -1
  54. package/dist/index.mjs.map +0 -1
  55. package/dist/login-renderer.cjs +0 -2
  56. package/dist/login-renderer.cjs.map +0 -1
  57. package/dist/login-renderer.mjs +0 -2
  58. package/dist/login-renderer.mjs.map +0 -1
  59. package/dist/login-renderer.vue.d.ts +0 -37
  60. package/dist/types.cjs.map +0 -1
  61. package/dist/types.mjs.map +0 -1
package/README.md CHANGED
@@ -1,106 +1,206 @@
1
1
  # @strivacity/sdk-vue
2
2
 
3
- A Vue 3 plugin 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
+ Vue 3 SDK for [Strivacity](https://www.strivacity.com) - adds PKCE-protected OIDC authentication to your Vue application with reactive composables.
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/vue) - Working Vue 3 example covering all four login modes
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
+ - [Quick start](#quick-start)
18
+ - [Authentication modes](#authentication-modes)
19
+ - [redirect mode](#redirect-mode)
20
+ - [popup mode](#popup-mode)
21
+ - [embedded mode](#embedded-mode)
22
+ - [native mode](#native-mode)
23
+ - [Composables API](#composables-api)
24
+ - [useStrivacity](#usestrivacity)
25
+ - [useNativeLogin](#usenativelogin)
26
+ - [Route guards](#route-guards)
27
+ - [Token management](#token-management)
28
+ - [Shared features](#shared-features)
29
+ - [Configuration reference](#configuration-reference)
30
+ - [Migration guide](#migration-guide)
31
+ - [Vulnerability Reporting](#vulnerability-reporting)
32
+ - [License](#license)
33
+ - [Contributing](#contributing)
4
34
 
5
- See our [Developer Portal](https://www.strivacity.com/learn-support/developer-hub) to get started with developing with the Strivacity product.
6
-
7
- ## Overview
8
-
9
- This SDK allows you to integrate Strivacity's policy-driven journeys into your Vue 3 application. It wraps the `@strivacity/sdk-core` library as a Vue plugin and exposes a `useStrivacity` composable that provides reactive authentication state and methods throughout your application. 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).
10
-
11
- ## Demo Application
35
+ ---
12
36
 
13
- - [Example app](https://github.com/Strivacity/sdk-js/tree/main/apps/vue)
14
- - [Ionic Example app](https://github.com/Strivacity/sdk-js/tree/main/apps/ionic-vue)
37
+ ## Prerequisites
15
38
 
16
- ## Requirements
39
+ - Vue 3.x
40
+ - A Strivacity tenant with an application configured (issuer URL, client ID, redirect URI)
17
41
 
18
- - Vue.js: 3+
42
+ ---
19
43
 
20
- ## Install
44
+ ## Installation
21
45
 
22
46
  ```bash
23
47
  npm install @strivacity/sdk-vue
24
48
  ```
25
49
 
26
- ## Usage
50
+ ---
51
+
52
+ ## Choosing a mode
53
+
54
+ The SDK supports **four authentication modes**:
27
55
 
28
- ### Initialization
56
+ | Mode | Login UI | Best for |
57
+ | ---------- | ---------------------------------------- | -------------------------------------------- |
58
+ | `redirect` | Strivacity hosted page | Standard web apps |
59
+ | `popup` | Strivacity hosted page in a popup | SPAs that must stay on the current page |
60
+ | `embedded` | Strivacity web components in your page | Branded login inside your own layout |
61
+ | `native` | Your own components driven by flow state | Full UI control, step-by-step form rendering |
29
62
 
30
- Register the SDK as a Vue plugin using `createStrivacitySDK` in your application's entry point:
63
+ > 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.
31
64
 
32
- ```js
65
+ ---
66
+
67
+ ## Quick start
68
+
69
+ ### 1. Install the plugin
70
+
71
+ `createStrivacitySDK` returns a Vue [plugin](https://vuejs.org/guide/reusability/plugins.html) - install it once on the root app instance. It initializes the SDK and provides the auth context to every component in the tree via `provide`/`inject`.
72
+
73
+ ```ts
74
+ // main.ts
33
75
  import { createApp } from 'vue';
34
- import App from './App.vue';
35
76
  import { createStrivacitySDK } from '@strivacity/sdk-vue';
77
+ import App from './App.vue';
36
78
 
37
79
  const app = createApp(App);
38
- const sdk = createStrivacitySDK({
39
- mode: 'redirect', // or 'popup', 'native', 'embedded'
40
- issuer: 'https://<YOUR_DOMAIN>',
41
- scopes: ['openid', 'profile'],
42
- clientId: '<YOUR_CLIENT_ID>',
43
- redirectUri: '<YOUR_REDIRECT_URI>',
44
- });
45
80
 
46
- app.use(sdk);
81
+ app.use(
82
+ createStrivacitySDK({
83
+ mode: 'redirect', // authentication mode
84
+ issuer: 'https://<YOUR_TENANT_DOMAIN>', // OIDC provider URL
85
+ clientId: 'YOUR_CLIENT_ID', // OAuth2 client ID
86
+ redirectUri: 'https://your-app.example.com/callback', // callback URL after authentication
87
+ scopes: ['openid', 'profile', 'email'], // requested user permissions/data
88
+ }),
89
+ );
90
+
47
91
  app.mount('#app');
48
92
  ```
49
93
 
50
- Use the `useStrivacity` composable in any component to access authentication state:
94
+ ### 2. Use the auth state
95
+
96
+ Call `useStrivacity()` in any component to read the current authentication state and trigger login or logout.
51
97
 
52
98
  ```vue
53
- <script setup>
99
+ <script setup lang="ts">
54
100
  import { useStrivacity } from '@strivacity/sdk-vue';
55
101
 
56
- const { loading, isAuthenticated, idTokenClaims } = useStrivacity();
102
+ const { loading, isAuthenticated, idTokenClaims, login, logout } = useStrivacity();
57
103
  </script>
104
+
105
+ <template>
106
+ <div v-if="!loading">
107
+ <template v-if="isAuthenticated">
108
+ <span>Hello, {{ idTokenClaims?.given_name }}</span>
109
+ <button @click="logout()">Log out</button>
110
+ </template>
111
+ <button v-else @click="login()">Log in</button>
112
+ </div>
113
+ </template>
58
114
  ```
59
115
 
60
- ### Redirect / Popup mode
116
+ ---
117
+
118
+ ## Authentication modes
119
+
120
+ ### redirect mode
121
+
122
+ > For details on how this mode works, see the [hosted journey documentation](https://docs.strivacity.com/reference/hosted-journey).
123
+
124
+ The current browser tab navigates to the Strivacity-hosted login page and back to the configured `redirectUri` after authentication.
125
+
126
+ ```ts
127
+ // main.ts
128
+ import { createApp } from 'vue';
129
+ import { createStrivacitySDK } from '@strivacity/sdk-vue';
130
+ import App from './App.vue';
131
+
132
+ const app = createApp(App);
133
+
134
+ app.use(
135
+ createStrivacitySDK({
136
+ mode: 'redirect', // authentication mode
137
+ issuer: 'https://<YOUR_TENANT_DOMAIN>', // OIDC provider URL
138
+ clientId: 'YOUR_CLIENT_ID', // OAuth2 client ID
139
+ redirectUri: 'https://your-app.example.com/callback', // callback URL after authentication
140
+ scopes: ['openid', 'profile', 'email'], // requested user permissions/data
141
+ }),
142
+ );
143
+
144
+ app.mount('#app');
145
+ ```
61
146
 
62
- 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.
147
+ #### Login
63
148
 
64
- #### Login page example
149
+ Call this to start the login flow. It redirects the user to the Strivacity login page in current browser tab, where they authenticate.
65
150
 
66
151
  ```vue
67
- <script setup>
152
+ <!-- pages/Login.vue -->
153
+ <script setup lang="ts">
68
154
  import { onMounted } from 'vue';
69
155
  import { useStrivacity } from '@strivacity/sdk-vue';
156
+ import type { RedirectFlow } from '@strivacity/sdk-vue';
70
157
 
71
- const { login } = useStrivacity();
158
+ const { login } = useStrivacity<RedirectFlow>();
72
159
 
73
- onMounted(() => {
74
- login();
160
+ onMounted(async () => {
161
+ await login({
162
+ // Optional parameters
163
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
164
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
165
+ audiences: ['https://api.example.com'], // target resources for the access token
166
+ locationMethod: 'assign', // window.location method: 'assign' adds to browser history, 'replace' doesn't (default: 'assign')
167
+ targetWindow: 'self', // 'self' redirects current window, 'top' redirects top-level window (default: 'self')
168
+ });
75
169
  });
76
170
  </script>
77
171
 
78
172
  <template>
79
173
  <section>
80
- <h1>Redirecting...</h1>
174
+ <h1>Redirecting to login...</h1>
81
175
  </section>
82
176
  </template>
83
177
  ```
84
178
 
85
- #### Callback page example
179
+ #### Handle the callback
86
180
 
87
- The callback page handles the response from the identity provider. It calls `handleCallback()` and redirects to `/profile` on success:
181
+ Call this on your redirect URI page 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](#storages). After that you can redirect to a protected page or render your app.
88
182
 
89
183
  ```vue
90
- <script setup>
184
+ <!-- pages/Callback.vue -->
185
+ <script setup lang="ts">
186
+ import { useStrivacity } from '@strivacity/sdk-vue';
91
187
  import { onMounted } from 'vue';
92
188
  import { useRouter } from 'vue-router';
93
- import { useStrivacity } from '@strivacity/sdk-vue';
94
189
 
95
- const router = useRouter();
96
190
  const { handleCallback } = useStrivacity();
191
+ const router = useRouter();
192
+ const searchParams = new URLSearchParams(window.location.search);
97
193
 
98
194
  onMounted(async () => {
195
+ if (searchParams.get('error')) {
196
+ return await router.replace(`/error?${searchParams.toString()}`);
197
+ }
198
+
99
199
  try {
100
200
  await handleCallback();
101
201
  await router.push('/profile');
102
202
  } catch (error) {
103
- console.error('Error during callback handling:', error);
203
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
104
204
  }
105
205
  });
106
206
  </script>
@@ -112,63 +212,49 @@ onMounted(async () => {
112
212
  </template>
113
213
  ```
114
214
 
115
- #### Profile page example
215
+ > The callback URL is automatically read from `window.location.href` if not provided. You can pass a custom URL as the first parameter: `await sdk.handleCallback(customUrl)`.
216
+
217
+ #### Registration
218
+
219
+ Call this to start the registration flow. It works the same way as `login()` but opens the registration form instead.
116
220
 
117
221
  ```vue
118
- <script setup>
222
+ <!-- pages/Register.vue -->
223
+ <script setup lang="ts">
224
+ import { onMounted } from 'vue';
119
225
  import { useStrivacity } from '@strivacity/sdk-vue';
226
+ import type { RedirectFlow } from '@strivacity/sdk-vue';
120
227
 
121
- const { loading, isAuthenticated, accessToken, accessTokenExpired, accessTokenExpirationDate, idTokenClaims, refreshToken } = useStrivacity();
228
+ const { register } = useStrivacity<RedirectFlow>();
229
+
230
+ onMounted(async () => {
231
+ await register({
232
+ loginHint: 'user@example.com',
233
+ });
234
+ });
122
235
  </script>
123
236
 
124
237
  <template>
125
238
  <section>
126
- <h1 v-if="loading">Loading...</h1>
127
- <dl v-else>
128
- <dt><strong>accessToken</strong></dt>
129
- <dd>
130
- <pre>{{ JSON.stringify(accessToken) }}</pre>
131
- </dd>
132
- <dt><strong>refreshToken</strong></dt>
133
- <dd>
134
- <pre>{{ JSON.stringify(refreshToken) }}</pre>
135
- </dd>
136
- <dt><strong>accessTokenExpired</strong></dt>
137
- <dd>
138
- <pre>{{ JSON.stringify(accessTokenExpired) }}</pre>
139
- </dd>
140
- <dt><strong>accessTokenExpirationDate</strong></dt>
141
- <dd>
142
- <pre>{{ accessTokenExpirationDate ? new Date(accessTokenExpirationDate * 1000).toLocaleString() : JSON.stringify(null) }}</pre>
143
- </dd>
144
- <dt><strong>claims</strong></dt>
145
- <dd>
146
- <pre>{{ JSON.stringify(idTokenClaims, null, 2) }}</pre>
147
- </dd>
148
- </dl>
239
+ <h1>Redirecting to registration...</h1>
149
240
  </section>
150
241
  </template>
151
242
  ```
152
243
 
153
- #### Logout page example
244
+ #### Logout
154
245
 
155
- 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.
246
+ 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`.
156
247
 
157
248
  ```vue
158
- <script setup>
159
- import { onMounted } from 'vue';
160
- import { useRouter } from 'vue-router';
249
+ <!-- pages/Logout.vue -->
250
+ <script setup lang="ts">
161
251
  import { useStrivacity } from '@strivacity/sdk-vue';
252
+ import { onMounted } from 'vue';
162
253
 
163
- const router = useRouter();
164
- const { isAuthenticated, logout } = useStrivacity();
254
+ const { logout } = useStrivacity();
165
255
 
166
256
  onMounted(async () => {
167
- if (isAuthenticated.value) {
168
- await logout({ postLogoutRedirectUri: location.origin });
169
- } else {
170
- await router.push('/');
171
- }
257
+ await logout();
172
258
  });
173
259
  </script>
174
260
 
@@ -179,506 +265,1403 @@ onMounted(async () => {
179
265
  </template>
180
266
  ```
181
267
 
182
- #### Component example
268
+ > The `postLogoutRedirectUri` must be configured in your Strivacity application client settings as an allowed post-logout redirect URI. If the URL is invalid or not provided, the user remains on the Strivacity-hosted logged out page.
269
+
270
+ #### Token management
271
+
272
+ Call these methods to manage the session and access token.
183
273
 
184
274
  ```vue
185
- <script setup>
186
- import { computed } from 'vue';
275
+ <script setup lang="ts">
187
276
  import { useStrivacity } from '@strivacity/sdk-vue';
277
+ import { computed } from 'vue';
188
278
 
189
- const { isAuthenticated, idTokenClaims, login, logout } = useStrivacity();
190
- const name = computed(() => `${idTokenClaims.value?.given_name} ${idTokenClaims.value?.family_name}`);
191
- </script>
279
+ const { idTokenClaims, accessToken, refreshToken, init, refresh, revoke } = useStrivacity();
192
280
 
193
- <template>
194
- <div v-if="isAuthenticated">
195
- <div>Welcome, {{ name }}!</div>
196
- <button @click="logout()">Logout</button>
197
- </div>
198
- <div v-else>
199
- <div>Not logged in</div>
200
- <button @click="login()">Log in</button>
201
- </div>
202
- </template>
281
+ // Refresh the access token using the refresh token
282
+ // (automatically waits for SDK initialization and session loading)
283
+ await refresh();
284
+
285
+ // Revoke all tokens at the authorization server and clear the local session
286
+ // (automatically waits for SDK initialization and session loading)
287
+ await revoke();
288
+
289
+ // Get the current access and refresh tokens (synchronous property - ensure SDK is initialized first)
290
+ await init();
291
+ idTokenClaims.value // read-only property - returns the current ID token claims or null if not authenticated
292
+ accessToken.value; // read-only property - returns the current access token or null if not authenticated
293
+ refreshToken.value; // read-only property - returns the current refresh token or null if not authenticated
294
+ </script>
203
295
  ```
204
296
 
205
- ### Native mode
297
+ ---
206
298
 
207
- In `native` mode the `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/vue/src/components/widgets).
299
+ ### popup mode
208
300
 
209
- The example widgets use SCSS for styling and Luxon for date handling:
301
+ > For details on how this mode works, see the [hosted journey documentation](https://docs.strivacity.com/reference/hosted-journey).
210
302
 
211
- ```bash
212
- npm install sass luxon
213
- npm install --save-dev @types/luxon
214
- ```
215
-
216
- ```js
217
- import CheckboxWidget from './checkbox.widget.vue';
218
- import DateWidget from './date.widget.vue';
219
- import InputWidget from './input.widget.vue';
220
- import LayoutWidget from './layout.widget.vue';
221
- import MultiSelectWidget from './multiselect.widget.vue';
222
- import PasscodeWidget from './passcode.widget.vue';
223
- import LoadingWidget from './loading.widget.vue';
224
- import PasswordWidget from './password.widget.vue';
225
- import PhoneWidget from './phone.widget.vue';
226
- import SelectWidget from './select.widget.vue';
227
- import StaticWidget from './static.widget.vue';
228
- import SubmitWidget from './submit.widget.vue';
229
-
230
- export const widgets = {
231
- checkbox: CheckboxWidget,
232
- date: DateWidget,
233
- input: InputWidget,
234
- layout: LayoutWidget,
235
- loading: LoadingWidget,
236
- passcode: PasscodeWidget,
237
- password: PasswordWidget,
238
- phone: PhoneWidget,
239
- select: SelectWidget,
240
- multiSelect: MultiSelectWidget,
241
- static: StaticWidget,
242
- submit: SubmitWidget,
243
- };
303
+ 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.
304
+
305
+ ```ts
306
+ // main.ts
307
+ import { createApp } from 'vue';
308
+ import { createStrivacitySDK, createDefaultLogging } from '@strivacity/sdk-vue';
309
+ import App from './App.vue';
310
+
311
+ const app = createApp(App);
312
+
313
+ app.use(
314
+ createStrivacitySDK({
315
+ mode: 'popup', // authentication mode
316
+ issuer: 'https://<YOUR_TENANT_DOMAIN>', // OIDC provider URL
317
+ clientId: 'YOUR_CLIENT_ID', // OAuth2 client ID
318
+ redirectUri: 'https://your-app.example.com/callback', // callback URL after authentication
319
+ scopes: ['openid', 'profile', 'email'], // requested user permissions/data
320
+ }),
321
+ );
322
+
323
+ app.mount('#app');
244
324
  ```
245
325
 
246
- #### Login page example
326
+ #### Login
327
+
328
+ 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](#storages).
247
329
 
248
- The login page extracts `session_id`, `short_app_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`.
330
+ By default a centered popup window opens. Pass `popupWindowTarget` to change where the window opens, and `popupWindowFeatures` to control its size and position:
249
331
 
250
332
  ```vue
333
+ <!-- pages/Login.vue -->
251
334
  <script setup lang="ts">
252
- import { ref } from 'vue';
335
+ import { onMounted } from 'vue';
253
336
  import { useRouter } from 'vue-router';
254
- import { FallbackError, useStrivacity, type LoginFlowState } from '@strivacity/sdk-vue';
255
- import { widgets } from './components/widgets';
337
+ import { useStrivacity } from '@strivacity/sdk-vue';
338
+ import type { PopupFlow } from '@strivacity/sdk-vue';
256
339
 
257
340
  const router = useRouter();
258
- const sessionId = ref<string | null>(null);
259
- const language = ref<string | null>(null);
260
-
261
- if (window.location.search !== '') {
262
- const url = new URL(window.location.href);
263
- sessionId.value = url.searchParams.get('session_id');
264
-
265
- if (url.searchParams.has('language')) {
266
- language.value = url.searchParams.get('language');
267
- }
268
-
269
- url.search = '';
270
- history.replaceState({}, '', url.toString());
271
- }
341
+ const { login } = useStrivacity<PopupFlow>();
272
342
 
273
- const onLogin = async () => {
274
- await router.push('/profile');
275
- };
343
+ onMounted(async () => {
344
+ try {
345
+ await login({
346
+ // Optional parameters
347
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
348
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
349
+ audiences: ['https://api.example.com'], // target resources for the access token
350
+ popupWindowTarget: '_blank', // any valid browsing context name
351
+ popupWindowFeatures: {
352
+ width: 500,
353
+ height: 700,
354
+ left: 100,
355
+ top: 100,
356
+ toolbar: false,
357
+ location: false,
358
+ resizable: true,
359
+ scrollbars: true,
360
+ },
361
+ });
276
362
 
277
- const onFallback = (error: FallbackError) => {
278
- if (error.url) {
279
- window.location.href = error.url.toString();
280
- } else {
281
- alert(error);
363
+ // User authenticated - navigate to a protected page
364
+ window.location.href = '/profile';
365
+ } catch (error) {
366
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
282
367
  }
283
- };
284
-
285
- const onError = (error: string) => {
286
- alert(error);
287
- };
288
-
289
- const onGlobalMessage = (message: string) => {
290
- alert(message);
291
- };
292
-
293
- const onBlockReady = ({ previousState, state }: { previousState: LoginFlowState; state: LoginFlowState }) => {
294
- console.log('previousState', previousState);
295
- console.log('state', state);
296
- };
368
+ });
297
369
  </script>
298
370
 
299
371
  <template>
300
- <StyLoginRenderer
301
- v-model:language="language"
302
- :widgets="widgets"
303
- :session-id="sessionId"
304
- @fallback="onFallback"
305
- @login="onLogin"
306
- @error="onError"
307
- @global-message="onGlobalMessage"
308
- @block-ready="onBlockReady"
309
- />
372
+ <section>
373
+ <h1>Opening login popup...</h1>
374
+ </section>
310
375
  </template>
311
376
  ```
312
377
 
313
- #### Callback page example
378
+ #### Handle the callback
314
379
 
315
- 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:
380
+ The popup resolves automatically - no callback page is needed. Token exchange happens inside the popup and the result is posted back to the opener window.
381
+
382
+ #### Registration
383
+
384
+ Call this to start the registration flow. It works the same way as `login()` but opens the registration form instead.
316
385
 
317
386
  ```vue
318
- <script setup>
319
- import { onMounted, computed } from 'vue';
387
+ <!-- pages/Register.vue -->
388
+ <script setup lang="ts">
389
+ import { onMounted } from 'vue';
320
390
  import { useRouter } from 'vue-router';
321
391
  import { useStrivacity } from '@strivacity/sdk-vue';
392
+ import type { PopupFlow } from '@strivacity/sdk-vue';
322
393
 
323
- const query = computed(() => Object.fromEntries(new URLSearchParams(window.location.search)));
324
394
  const router = useRouter();
325
- const { handleCallback } = useStrivacity();
395
+ const { register } = useStrivacity<PopupFlow>();
326
396
 
327
397
  onMounted(async () => {
328
- const url = new URL(location.href);
329
- const sessionId = url.searchParams.get('session_id');
330
-
331
- if (sessionId) {
332
- await router.push(`/login?session_id=${sessionId}`);
333
- } else {
334
- try {
335
- await handleCallback();
336
- await router.push('/profile');
337
- } catch (error) {
338
- console.error('Error during callback handling:', error);
339
- }
398
+ try {
399
+ await register({
400
+ // Optional parameters
401
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
402
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
403
+ audiences: ['https://api.example.com'], // target resources for the access token
404
+ popupWindowTarget: '_blank', // any valid browsing context name
405
+ popupWindowFeatures: {
406
+ width: 500,
407
+ height: 700,
408
+ left: 100,
409
+ top: 100,
410
+ toolbar: false,
411
+ location: false,
412
+ resizable: true,
413
+ scrollbars: true,
414
+ },
415
+ });
416
+
417
+ // User authenticated - navigate to a protected page
418
+ window.location.href = '/profile';
419
+ } catch (error) {
420
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
340
421
  }
341
422
  });
342
423
  </script>
343
424
 
344
425
  <template>
345
- <section v-if="query.error">
346
- <h1>Error in authentication</h1>
347
- <div>
348
- <h4>{{ query.error }}</h4>
349
- <p>{{ query.error_description }}</p>
350
- </div>
351
- </section>
352
- <section v-else>
353
- <h1>Logging in...</h1>
426
+ <section>
427
+ <h1>Opening registration popup...</h1>
354
428
  </section>
355
429
  </template>
356
430
  ```
357
431
 
358
- #### Entry page example
432
+ #### Logout
359
433
 
360
- 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 login page:
434
+ 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`.
361
435
 
362
436
  ```vue
363
- <script setup>
364
- import { onMounted } from 'vue';
365
- import { useRouter } from 'vue-router';
437
+ <!-- pages/Logout.vue -->
438
+ <script setup lang="ts">
366
439
  import { useStrivacity } from '@strivacity/sdk-vue';
440
+ import { onMounted } from 'vue';
367
441
 
368
- const router = useRouter();
369
- const { entry } = useStrivacity();
442
+ const { logout } = useStrivacity();
370
443
 
371
444
  onMounted(async () => {
372
- try {
373
- const data = await entry();
374
-
375
- if (data && Object.keys(data).length > 0) {
376
- await router.push(`/callback?${new URLSearchParams(data).toString()}`);
377
- } else {
378
- await router.push('/');
379
- }
380
- } catch (error) {
381
- console.error('Entry failed:', error);
382
- await router.push('/');
383
- }
445
+ await logout();
384
446
  });
385
447
  </script>
448
+
449
+ <template>
450
+ <section>
451
+ <h1>Logging out...</h1>
452
+ </section>
453
+ </template>
386
454
  ```
387
455
 
388
- #### Profile page example
456
+ > The `postLogoutRedirectUri` must be configured in your Strivacity application client settings as an allowed post-logout redirect URI. If the URL is invalid or not provided, the user remains on the Strivacity-hosted logged out page.
457
+
458
+ #### Token management
389
459
 
390
- Same as the profile page example in redirect/popup mode.
460
+ Call these methods to manage the session and access token.
391
461
 
392
- #### Logout page example
462
+ ```vue
463
+ <script setup lang="ts">
464
+ import { useStrivacity } from '@strivacity/sdk-vue';
465
+ import { computed } from 'vue';
466
+
467
+ const { idTokenClaims, accessToken, refreshToken, init, refresh, revoke } = useStrivacity();
468
+
469
+ // Refresh the access token using the refresh token
470
+ // (automatically waits for SDK initialization and session loading)
471
+ await refresh();
472
+
473
+ // Revoke all tokens at the authorization server and clear the local session
474
+ // (automatically waits for SDK initialization and session loading)
475
+ await revoke();
476
+
477
+ // Get the current access and refresh tokens (synchronous property - ensure SDK is initialized first)
478
+ await init();
479
+ idTokenClaims.value // read-only property - returns the current ID token claims or null if not authenticated
480
+ accessToken.value; // read-only property - returns the current access token or null if not authenticated
481
+ refreshToken.value; // read-only property - returns the current refresh token or null if not authenticated
482
+ </script>
483
+ ```
484
+
485
+ ---
393
486
 
394
- Same as the logout page example in redirect/popup mode.
487
+ ### embedded mode
395
488
 
396
- ### Embedded mode
489
+ > For details on how this mode works, see the [embedded journey documentation](https://docs.strivacity.com/reference/embedded-journey).
397
490
 
398
- In `embedded` mode the `<sty-login>` web component (loaded via `bundle.js` from the cluster) handles rendering. Import the bundle at application startup to register the Strivacity web components:
491
+ 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 during application bootstrap, alongside SDK initialization:
399
492
 
400
- ```js
493
+ ```ts
494
+ // main.ts
401
495
  import { createApp } from 'vue';
402
- import App from './App.vue';
403
496
  import { createStrivacitySDK } from '@strivacity/sdk-vue';
497
+ import { injectScript } from '@strivacity/sdk-vue';
404
498
 
405
- void import(`${import.meta.env.VITE_ISSUER}/assets/components/bundle.js`);
499
+ // Load web components bundle
500
+ injectScript('sty-components', 'https://<YOUR_TENANT_DOMAIN>/assets/components/bundle.js');
406
501
 
407
502
  const app = createApp(App);
408
- const sdk = createStrivacitySDK({
409
- mode: 'embedded',
410
- issuer: 'https://<YOUR_DOMAIN>',
411
- scopes: ['openid', 'profile'],
412
- clientId: '<YOUR_CLIENT_ID>',
413
- redirectUri: '<YOUR_REDIRECT_URI>',
414
- });
415
503
 
416
- app.use(sdk);
504
+ app.use(
505
+ createStrivacitySDK({
506
+ mode: 'embedded', // authentication mode
507
+ issuer: 'https://<YOUR_TENANT_DOMAIN>', // OIDC provider URL
508
+ clientId: 'YOUR_CLIENT_ID', // OAuth2 client ID
509
+ redirectUri: 'https://your-app.example.com/callback', // callback URL after authentication
510
+ scopes: ['openid', 'profile', 'email'], // requested user permissions/data
511
+ }),
512
+ );
513
+
417
514
  app.mount('#app');
418
515
  ```
419
516
 
420
- #### Login page example
517
+ #### Login / Register
421
518
 
422
- The login page extracts `session_id`, `short_app_id`, and optionally `language` from the URL on load and passes them to the `<sty-login>` web component. When a `language` parameter is present it overrides `uiLocales` to display the authentication UI in the specified language. The bundle registers `<sty-login>`, `<sty-notifications>`, and `<sty-language-selector>` as custom elements.
519
+ In embedded mode `<sty-login>` web component does not take `issuer`, `clientId`, or `redirectUri` as attributes - those come from the SDK configuration above. Place it on your login page together with `sty-notifications` (toast-style system notifications) and `sty-language-selector` (a language switcher for the login flow):
423
520
 
424
521
  ```vue
522
+ <!-- pages/Login.vue -->
425
523
  <script setup lang="ts">
426
- import { ref } from 'vue';
427
524
  import { useRouter } from 'vue-router';
428
- import { useStrivacity } from '@strivacity/sdk-vue';
429
525
 
430
526
  const router = useRouter();
431
- const shortAppId = ref<string | null>(null);
432
- const sessionId = ref<string | null>(null);
527
+ const searchParams = new URLSearchParams(window.location.search);
528
+
529
+ // Optional: Resume a session started from an entry URL (e.g., password reset)
530
+ const sessionId = searchParams.get('session_id');
531
+ const shortAppId = searchParams.get('short_app_id');
532
+ const lang = searchParams.get('language') ?? navigator.language;
533
+
534
+ const params = {
535
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
536
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
537
+ audiences: ['https://api.example.com'], // target resources for the access token
538
+ language: 'en-US', // set the UI language (BCP 47 language tag)
539
+ prompt: 'login', // use 'create' to open the registration flow instead
540
+ };
433
541
 
434
- if (location.search !== '') {
435
- const url = new URL(window.location.href);
436
- shortAppId.value = url.searchParams.get('short_app_id');
437
- sessionId.value = url.searchParams.get('session_id');
438
- url.search = '';
439
- history.replaceState({}, '', url.toString());
542
+ async function onLogin() {
543
+ await router.push('/profile');
440
544
  }
441
545
 
442
- const onLogin = async () => {
443
- await router.push('/profile');
444
- };
445
- const onClose = () => {
446
- location.reload();
447
- };
448
- const onError = (event: CustomEvent) => {
449
- alert(event.detail);
450
- };
546
+ function onClose() {
547
+ window.location.reload();
548
+ }
549
+
550
+ async function onError(event: CustomEvent) {
551
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
552
+ }
451
553
  </script>
452
554
 
453
555
  <template>
454
- <sty-notifications></sty-notifications>
455
- <sty-login :shortAppId="shortAppId" :sessionId="sessionId" @close="onClose" @login="onLogin" @error="onError($event.detail)"></sty-login>
456
- <sty-language-selector></sty-language-selector>
556
+ <section>
557
+ <sty-notifications></sty-notifications>
558
+ <sty-login
559
+ :params.prop="params"
560
+ :sessionId="sessionId"
561
+ :shortAppId="shortAppId"
562
+ :lang="lang"
563
+ @login="onLogin"
564
+ @close="onClose"
565
+ @error="onError"
566
+ ></sty-login>
567
+ <sty-language-selector></sty-language-selector>
568
+ </section>
457
569
  </template>
458
570
  ```
459
571
 
460
- #### Callback page example
461
-
462
- Same as the callback page example in native mode.
572
+ ##### Controlling when the flow starts
463
573
 
464
- #### Entry page example
574
+ 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 the component mounts:
465
575
 
466
- Same as the entry page example in native mode.
576
+ ```vue
577
+ <!-- pages/Login.vue -->
578
+ <script setup lang="ts">
579
+ import { useTemplateRef } from 'vue';
580
+ import { useRouter } from 'vue-router';
581
+ import type { LoginComponent } from '@strivacity/sdk-vue/types';
467
582
 
468
- #### Profile page example
583
+ const router = useRouter();
584
+ const loginEl = useTemplateRef<LoginComponent>('loginEl');
585
+
586
+ async function onStartClick() {
587
+ await loginEl.value?.start({
588
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
589
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
590
+ audiences: ['https://api.example.com'], // target resources for the access token
591
+ language: 'en-US', // set the UI language (BCP 47 language tag)
592
+ prompt: 'login', // use 'create' to open the registration flow instead
593
+ });
594
+ }
469
595
 
470
- Same as the profile page example in native mode.
596
+ async function onLogin() {
597
+ await router.push('/profile');
598
+ }
471
599
 
472
- #### Logout page example
600
+ async function onError(event: CustomEvent) {
601
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
602
+ }
603
+ </script>
473
604
 
474
- Same as the logout page example in native mode.
605
+ <template>
606
+ <section>
607
+ <sty-notifications></sty-notifications>
608
+ <button @click="onStartClick">Continue to login</button>
609
+ <sty-login ref="loginEl" lazy @login="onLogin" @error="onError"></sty-login>
610
+ <sty-language-selector></sty-language-selector>
611
+ </section>
612
+ </template>
613
+ ```
475
614
 
476
- ## Logging
615
+ You can also set params via the `params` property before calling `start()`:
477
616
 
478
- 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.
617
+ ```vue
618
+ <script setup lang="ts">
619
+ import { useTemplateRef } from 'vue';
620
+ import type { LoginComponent } from '@strivacity/sdk-vue/types';
479
621
 
480
- ### Using the Default Logger
622
+ const loginEl = useTemplateRef<LoginComponent>('loginEl');
481
623
 
482
- Enable the default console logger by adding the `logging` option when creating the SDK:
624
+ async function onStartClick() {
625
+ if (!loginEl.value) {
626
+ return;
627
+ }
483
628
 
484
- ```js
485
- import { createStrivacitySDK, DefaultLogging } from '@strivacity/sdk-vue';
629
+ loginEl.value.params = {
630
+ loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
631
+ acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
632
+ audiences: ['https://api.example.com'], // target resources for the access token
633
+ language: 'en-US', // set the UI language (BCP 47 language tag)
634
+ prompt: 'login', // use 'create' to open the registration flow instead
635
+ };
636
+ await loginEl.value.start();
637
+ }
638
+ </script>
486
639
 
487
- const sdk = createStrivacitySDK({
488
- mode: 'redirect',
489
- issuer: 'https://<YOUR_DOMAIN>',
490
- scopes: ['openid', 'profile'],
491
- clientId: '<YOUR_CLIENT_ID>',
492
- redirectUri: '<YOUR_REDIRECT_URI>',
493
- logging: DefaultLogging,
494
- });
640
+ <template>
641
+ <sty-login ref="loginEl" lazy @login="onLogin"></sty-login>
642
+ <button @click="onStartClick">Start Login</button>
643
+ </template>
495
644
  ```
496
645
 
497
- The default logger writes to the browser console and automatically prefixes messages with a correlation ID when available (via the `xEventId` property).
646
+ ##### Login events
498
647
 
499
- ### Creating a Custom Logger
648
+ The `<sty-login>` element dispatches `login`, `close`, and `error` custom events. Listen to them using Vue's `@` directive:
500
649
 
501
- Implement the `SDKLogging` interface and pass your class to the `logging` option:
650
+ ```vue
651
+ <!-- pages/Login.vue -->
652
+ <script setup lang="ts">
653
+ import { useRouter } from 'vue-router';
502
654
 
503
- ```typescript
504
- import type { SDKLogging } from '@strivacity/sdk-vue';
655
+ const router = useRouter();
505
656
 
506
- export class MyLogger implements SDKLogging {
507
- xEventId?: string;
657
+ async function onLogin() {
658
+ // User authenticated - navigate to a protected page
659
+ await router.push('/profile');
660
+ }
508
661
 
509
- debug(message: string): void {
510
- console.debug(this.xEventId ? `[${this.xEventId}] ${message}` : message);
511
- }
662
+ function onClose() {
663
+ // User cancelled or closed the login flow
664
+ window.location.reload();
665
+ }
512
666
 
513
- info(message: string): void {
514
- console.info(this.xEventId ? `[${this.xEventId}] ${message}` : message);
515
- }
667
+ async function onError(event: CustomEvent) {
668
+ // A fatal error occurred - the message is available in event.detail
669
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
670
+ }
671
+ </script>
516
672
 
517
- warn(message: string): void {
518
- console.warn(this.xEventId ? `[${this.xEventId}] ${message}` : message);
519
- }
673
+ <template>
674
+ <section>
675
+ <sty-notifications></sty-notifications>
676
+ <sty-login
677
+ @login="onLogin"
678
+ @close="onClose"
679
+ @error="onError"
680
+ ></sty-login>
681
+ <sty-language-selector></sty-language-selector>
682
+ </section>
683
+ </template>
684
+ ```
685
+
686
+ ##### Notification events
520
687
 
521
- error(message: string, error: Error): void {
522
- console.error(this.xEventId ? `[${this.xEventId}] ${message}` : message, error);
688
+ 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:
689
+
690
+ ```vue
691
+ <script setup lang="ts">
692
+ import { onMounted, onUnmounted } from 'vue';
693
+
694
+ function onNotification(event: Event) {
695
+ const customEvent = event as CustomEvent;
696
+ if (customEvent.detail.action === 'show') {
697
+ // Add new notification to your custom notification system
698
+ const notification = customEvent.detail.notification;
699
+ console.log('New notification:', notification);
700
+ // Handle the notification display in your own UI
701
+ } else if (customEvent.detail.action === 'clear') {
702
+ // Clear all notifications
703
+ console.log('Clear all notifications');
523
704
  }
524
705
  }
525
- ```
526
706
 
527
- ```js
528
- import { createStrivacitySDK } from '@strivacity/sdk-vue';
529
- import { MyLogger } from './logging/MyLogger';
707
+ onMounted(() => {
708
+ document.addEventListener('notification', onNotification);
709
+ });
530
710
 
531
- const sdk = createStrivacitySDK({
532
- // ...other options
533
- logging: MyLogger,
711
+ onUnmounted(() => {
712
+ document.removeEventListener('notification', onNotification);
534
713
  });
714
+ </script>
535
715
  ```
536
716
 
537
- 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.
538
-
539
- ## HTTP Client
540
-
541
- 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.
717
+ ##### Dynamic language switching
542
718
 
543
- ### Adding custom headers to every request
719
+ 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:
544
720
 
545
- ```typescript
546
- import { createStrivacitySDK, SDKHttpClient, type HttpClientResponse } from '@strivacity/sdk-vue';
721
+ ```vue
722
+ <script setup lang="ts">
723
+ import { ref, useTemplateRef } from 'vue';
547
724
 
548
- class CustomHttpClient extends SDKHttpClient {
549
- async request<T>(url: string, options?: RequestInit): Promise<HttpClientResponse<T>> {
550
- const mergedOptions: RequestInit = {
551
- ...options,
552
- headers: {
553
- 'x-sty-app-id': 'my-app',
554
- ...(options?.headers as Record<string, string>),
555
- },
556
- };
725
+ const loginEl = useTemplateRef<HTMLElement & { lang: string }>('loginEl');
726
+ const currentLang = ref('en-US');
557
727
 
558
- const response = await fetch(url, mergedOptions);
728
+ function changeLanguage(lang: string) {
729
+ currentLang.value = lang;
559
730
 
560
- return {
561
- headers: response.headers,
562
- ok: response.ok,
563
- status: response.status,
564
- statusText: response.statusText,
565
- url: response.url,
566
- json: async () => (await response.json()) as T,
567
- text: async () => await response.text(),
568
- };
731
+ if (loginEl.value) {
732
+ loginEl.value.lang = lang;
569
733
  }
570
734
  }
735
+ </script>
571
736
 
572
- const sdk = createStrivacitySDK({
573
- // ...other options
574
- httpClient: CustomHttpClient,
575
- });
737
+ <template>
738
+ <section>
739
+ <div>
740
+ <button @click="changeLanguage('en-US')">English</button>
741
+ <button @click="changeLanguage('fr-FR')">Français</button>
742
+ <button @click="changeLanguage('de-DE')">Deutsch</button>
743
+ </div>
744
+ <sty-login ref="loginEl" :lang="currentLang"></sty-login>
745
+ </section>
746
+ </template>
576
747
  ```
577
748
 
578
- Any header you add inside `request()` is automatically included in every SDK request
579
-
580
- ### CORS configuration
749
+ #### Handle the callback
581
750
 
582
- 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.
583
-
584
- ```
585
- Access-Control-Allow-Headers: x-sty-app-id, <any other custom headers>
586
- ```
751
+ No separate callback page is needed. The `<sty-login>` component handles the entire authentication flow automatically, including token exchange, and dispatches a `login` event when authentication completes successfully.
587
752
 
588
- ## API Documentation
753
+ #### Externally-initiated flows (entry)
589
754
 
590
- ### `useStrivacity` composable
755
+ 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 page to resolve the flow parameters from the IDP (`session_id`, `short_app_id`, `language`).
591
756
 
592
- ```typescript
593
- useStrivacity<T extends PopupContext | RedirectContext | NativeContext>(): T;
594
- ```
757
+ You have two options:
595
758
 
596
- The composable returns a different context type depending on the `mode` configured when creating the SDK.
759
+ **Option 1: Redirect to a separate login page**
597
760
 
598
- **Shared properties (all modes)**
761
+ Forward the parameters as query params to your login page:
599
762
 
600
- - **`sdk: RedirectFlow | PopupFlow | NativeFlow`**: The underlying SDK flow instance.
601
- - **`loading: Ref<boolean>`**: `true` while the session is being initialized.
602
- - **`options: SDKOptions`**: The configured SDK options.
603
- - **`isAuthenticated: Ref<boolean>`**: `true` when the user has a valid session.
604
- - **`idTokenClaims: Ref<IdTokenClaims | null>`**: Claims from the ID token, or `null` if not authenticated.
605
- - **`accessToken: Ref<string | null>`**: The current access token.
606
- - **`refreshToken: Ref<string | null>`**: The current refresh token.
607
- - **`accessTokenExpired: Ref<boolean>`**: `true` when the access token has expired.
608
- - **`accessTokenExpirationDate: Ref<number | null>`**: Expiration timestamp (Unix ms) of the access token.
763
+ ```vue
764
+ <!-- pages/Entry.vue -->
765
+ <script setup lang="ts">
766
+ import { useStrivacity } from '@strivacity/sdk-vue';
767
+ import { useRouter } from 'vue-router';
768
+ import { onMounted } from 'vue';
769
+ import type { EmbeddedFlow } from '@strivacity/sdk-vue';
609
770
 
610
- ---
771
+ const { entry } = useStrivacity<EmbeddedFlow>();
772
+ const router = useRouter();
611
773
 
612
- **Type: `RedirectContext`**
774
+ onMounted(async () => {
775
+ try {
776
+ const data = await entry();
613
777
 
614
- - **`login(options?: LoginOptions): Promise<void>`**: Initiates login by redirecting to the identity provider.
615
- - **`register(options?: RegisterOptions): Promise<void>`**: Initiates registration using a redirect flow.
616
- - **`refresh(): Promise<void>`**: Refreshes the user's session.
617
- - **`revoke(): Promise<void>`**: Revokes the current session tokens.
618
- - **`logout(options?: LogoutOptions): Promise<void>`**: Logs the user out via redirect.
619
- - **`handleCallback(url?: string): Promise<void>`**: Processes the authorization callback after redirect.
620
- - **`entry(): Promise<Record<string, string>>`**: Processes an externally-initiated flow URL and returns the parameters needed to resume the flow (e.g. `session_id`, `short_app_id`).
778
+ // Redirect to login page with flow parameters
779
+ const params = new URLSearchParams({
780
+ session_id: data.session_id,
781
+ short_app_id: data.short_app_id,
782
+ language: data.language,
783
+ });
784
+ window.location.href = `/login?${params}`;
785
+ } catch (error) {
786
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
787
+ }
788
+ });
789
+ </script>
790
+
791
+ <template>
792
+ <section>
793
+ <h1>Loading...</h1>
794
+ </section>
795
+ </template>
796
+ ```
797
+
798
+ Then on your login page, read the parameters and pass them to `<sty-login>`:
799
+
800
+ ```vue
801
+ <!-- pages/Login.vue -->
802
+ <script setup lang="ts">
803
+ import { useRouter } from 'vue-router';
804
+
805
+ const router = useRouter();
806
+ const searchParams = new URLSearchParams(window.location.search);
807
+
808
+ // Read parameters from URL
809
+ const sessionId = searchParams.get('session_id');
810
+ const shortAppId = searchParams.get('short_app_id');
811
+ const language = searchParams.get('language');
812
+
813
+ async function onLogin() {
814
+ // User authenticated - navigate to a protected page
815
+ await router.push('/profile');
816
+ }
817
+
818
+ async function onError(event: CustomEvent) {
819
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
820
+ }
821
+ </script>
822
+
823
+ <template>
824
+ <section>
825
+ <sty-notifications></sty-notifications>
826
+ <sty-login
827
+ :sessionId="sessionId"
828
+ :shortAppId="shortAppId"
829
+ :lang="language"
830
+ @login="onLogin"
831
+ @error="onError"
832
+ ></sty-login>
833
+ <sty-language-selector></sty-language-selector>
834
+ </section>
835
+ </template>
836
+ ```
837
+
838
+ **Option 2: Render login on the entry page**
839
+
840
+ Pass the parameters directly to `<sty-login>` on the same page:
841
+
842
+ ```vue
843
+ <!-- pages/Entry.vue -->
844
+ <script setup lang="ts">
845
+ import { useStrivacity } from '@strivacity/sdk-vue';
846
+ import { useRouter } from 'vue-router';
847
+ import { onMounted, ref } from 'vue';
848
+ import type { EmbeddedFlow } from '@strivacity/sdk-vue';
849
+
850
+ const { entry } = useStrivacity<EmbeddedFlow>();
851
+ const router = useRouter();
852
+
853
+ const sessionId = ref<string | null>(null);
854
+ const shortAppId = ref<string | null>(null);
855
+ const language = ref<string | null>(null);
856
+
857
+ onMounted(async () => {
858
+ try {
859
+ const data = await entry();
860
+
861
+ // Set properties for sty-login component
862
+ sessionId.value = data.session_id;
863
+ shortAppId.value = data.short_app_id;
864
+ language.value = data.language;
865
+ } catch (error) {
866
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
867
+ }
868
+ });
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 v-if="sessionId">
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
+ <section v-else>
893
+ <h1>Loading...</h1>
894
+ </section>
895
+ </template>
896
+ ```
897
+
898
+ #### Logout
899
+
900
+ 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`.
901
+
902
+ ```vue
903
+ <!-- pages/Logout.vue -->
904
+ <script setup lang="ts">
905
+ import { useStrivacity } from '@strivacity/sdk-vue';
906
+ import { onMounted } from 'vue';
907
+
908
+ const { logout } = useStrivacity();
909
+
910
+ onMounted(async () => {
911
+ await logout();
912
+ });
913
+ </script>
914
+
915
+ <template>
916
+ <section>
917
+ <h1>Logging out...</h1>
918
+ </section>
919
+ </template>
920
+ ```
921
+
922
+ > The `postLogoutRedirectUri` must be configured in your Strivacity application client settings as an allowed post-logout redirect URI. If the URL is invalid or not provided, the user remains on the Strivacity-hosted logged out page.
923
+
924
+ #### Token management
925
+
926
+ Call these methods to manage the session and access token.
927
+
928
+ ```vue
929
+ <script setup lang="ts">
930
+ import { useStrivacity } from '@strivacity/sdk-vue';
931
+ import { computed } from 'vue';
932
+
933
+ const { idTokenClaims, accessToken, refreshToken, init, refresh, revoke } = useStrivacity();
934
+
935
+ // Refresh the access token using the refresh token
936
+ // (automatically waits for SDK initialization and session loading)
937
+ await refresh();
938
+
939
+ // Revoke all tokens at the authorization server and clear the local session
940
+ // (automatically waits for SDK initialization and session loading)
941
+ await revoke();
942
+
943
+ // Get the current access and refresh tokens (synchronous property - ensure SDK is initialized first)
944
+ await init();
945
+ idTokenClaims.value // read-only property - returns the current ID token claims or null if not authenticated
946
+ accessToken.value; // read-only property - returns the current access token or null if not authenticated
947
+ refreshToken.value; // read-only property - returns the current refresh token or null if not authenticated
948
+ </script>
949
+ ```
621
950
 
622
951
  ---
623
952
 
624
- **Type: `PopupContext`**
953
+ ### native mode
954
+
955
+ > For details on how this mode works, see the [native journey documentation](https://docs.strivacity.com/reference/native-journey).
956
+
957
+ You build the entire login UI with your own components. Call `startSession()` on the SDK instance to get the initial `NativeFlowState`, render the widgets, submit each form step with `submitForm()`, and repeat until `state.finalizeUrl` is set - then call `finalizeSession()`.
958
+
959
+ > The example below shows a simplified custom implementation. For a complete native renderer with all widget types, see the [example app](../../apps/vue/src/components/auth/native/NativeLogin.vue).
960
+
961
+ #### Login / Register
962
+
963
+ Create a login page using `useNativeLogin`:
964
+
965
+ ```vue
966
+ <!-- pages/Login.vue -->
967
+ <script setup lang="ts">
968
+ import { useRouter } from 'vue-router';
969
+ import { useNativeLogin } from '@strivacity/sdk-vue';
970
+
971
+ const router = useRouter();
972
+ const searchParams = new URLSearchParams(window.location.search);
973
+
974
+ const { state, forms, messages, loading, submitForm } = useNativeLogin({
975
+ params: {
976
+ prompt: 'login', // use 'create' to open the registration flow instead
977
+ language: 'en-US', // set the UI language (BCP 47 language tag)
978
+ sdk: 'web-minimal', // rendering mode: 'web-minimal' for simplified rendering (see below), 'web' (default) for full rendering hints and branding
979
+ sessionId: null, // pass a session ID to resume an existing flow
980
+ },
981
+ onLogin: async () => {
982
+ await router.push('/profile');
983
+ },
984
+ onClose: () => {
985
+ window.location.reload();
986
+ },
987
+ onError: async (error) => {
988
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
989
+ },
990
+ onFallback: (error) => {
991
+ // Fallback to hosted journey if native widget not supported
992
+ window.location.href = error.url.toString();
993
+ },
994
+ onGlobalMessage: (message) => {
995
+ alert(message.text);
996
+ },
997
+ });
998
+
999
+ async function handleSubmit(formId: string) {
1000
+ await submitForm(formId);
1001
+ }
1002
+ </script>
1003
+
1004
+ <template>
1005
+ <section v-if="loading">
1006
+ <h1>Loading...</h1>
1007
+ </section>
1008
+
1009
+ <section v-else-if="state.screen === 'identifier'">
1010
+ <h2>Sign In</h2>
1011
+ <form @submit.prevent="handleSubmit('identifier')">
1012
+ <input
1013
+ v-model="forms['identifier'].identifier"
1014
+ type="text"
1015
+ placeholder="Email"
1016
+ />
1017
+ <div v-if="messages['identifier'].identifier" class="error">
1018
+ {{ messages['identifier'].identifier.text }}
1019
+ </div>
1020
+ <button type="submit">Continue</button>
1021
+ </form>
1022
+ </section>
1023
+
1024
+ <section v-else-if="state.screen === 'password'">
1025
+ <h2>Enter Password</h2>
1026
+ <form @submit.prevent="handleSubmit('password')">
1027
+ <input
1028
+ v-model="forms['password'].password"
1029
+ type="password"
1030
+ placeholder="Password"
1031
+ />
1032
+ <div v-if="messages['password'].password" class="error">
1033
+ {{ messages['password'].password.text }}
1034
+ </div>
1035
+ <button type="submit">Sign In</button>
1036
+ </form>
1037
+ </section>
1038
+ </template>
1039
+ ```
1040
+
1041
+ #### Handle the callback
1042
+
1043
+ No separate callback page is needed. Once `state.finalizeUrl` is set, `submitForm()` automatically finalizes the session internally to exchange the authorization code for tokens and store the session.
1044
+
1045
+ #### Externally-initiated flows (entry)
1046
+
1047
+ 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 page to resolve the flow parameters from the IDP (`session_id`, `short_app_id`, `language`).
1048
+
1049
+ You have two options:
1050
+
1051
+ **Option 1: Redirect to a separate login page**
1052
+
1053
+ Forward the parameters as query params to your login page:
1054
+
1055
+ ```vue
1056
+ <!-- pages/Entry.vue -->
1057
+ <script setup lang="ts">
1058
+ import { useStrivacity } from '@strivacity/sdk-vue';
1059
+ import { useRouter } from 'vue-router';
1060
+ import { onMounted } from 'vue';
1061
+ import type { EmbeddedFlow } from '@strivacity/sdk-vue';
1062
+
1063
+ const { entry } = useStrivacity<EmbeddedFlow>();
1064
+ const router = useRouter();
1065
+
1066
+ onMounted(async () => {
1067
+ try {
1068
+ const data = await entry();
625
1069
 
626
- - **`login(options?: LoginOptions): Promise<void>`**: Initiates login using a popup window.
627
- - **`register(options?: RegisterOptions): Promise<void>`**: Initiates registration using a popup.
628
- - **`refresh(): Promise<void>`**: Refreshes the user's session.
629
- - **`revoke(): Promise<void>`**: Revokes the current session tokens.
630
- - **`logout(options?: LogoutOptions): Promise<void>`**: Logs the user out via popup.
631
- - **`handleCallback(url?: string): Promise<void>`**: Processes the authorization callback.
632
- - **`entry(): Promise<Record<string, string>>`**: Processes an externally-initiated flow URL and returns the parameters needed to resume the flow.
1070
+ // Redirect to login page with flow parameters
1071
+ const params = new URLSearchParams({
1072
+ session_id: data.session_id,
1073
+ short_app_id: data.short_app_id,
1074
+ language: data.language,
1075
+ });
1076
+ window.location.href = `/login?${params}`;
1077
+ } catch (error) {
1078
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
1079
+ }
1080
+ });
1081
+ </script>
1082
+
1083
+ <template>
1084
+ <section>
1085
+ <h1>Loading...</h1>
1086
+ </section>
1087
+ </template>
1088
+ ```
1089
+
1090
+ Then on your login page, read query parameters from the URL and pass it to `useNativeLogin` to resume the flow, exactly as shown in the Login / Register example above:
1091
+
1092
+ ```vue
1093
+ <!-- pages/Login.vue -->
1094
+ <script setup lang="ts">
1095
+ import { useRouter } from 'vue-router';
1096
+
1097
+ const router = useRouter();
1098
+ const searchParams = new URLSearchParams(window.location.search);
1099
+
1100
+ // Read parameters from URL
1101
+ const sessionId = searchParams.get('session_id');
1102
+ const shortAppId = searchParams.get('short_app_id');
1103
+ const language = searchParams.get('language');
1104
+
1105
+ async function onLogin() {
1106
+ // User authenticated - navigate to a protected page
1107
+ await router.push('/profile');
1108
+ }
1109
+
1110
+ async function onError(event: CustomEvent) {
1111
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
1112
+ }
1113
+ </script>
1114
+
1115
+ <template>
1116
+ <section>
1117
+ <sty-notifications></sty-notifications>
1118
+ <sty-login
1119
+ :sessionId="sessionId"
1120
+ :shortAppId="shortAppId"
1121
+ :lang="language"
1122
+ @login="onLogin"
1123
+ @error="onError"
1124
+ ></sty-login>
1125
+ <sty-language-selector></sty-language-selector>
1126
+ </section>
1127
+ </template>
1128
+ ```
1129
+
1130
+ **Option 2: Render login on the entry page**
1131
+
1132
+ Pass the parameters directly to `<sty-login>` on the same page:
1133
+
1134
+ ```vue
1135
+ <!-- pages/Entry.vue -->
1136
+ <script setup lang="ts">
1137
+ import { useStrivacity } from '@strivacity/sdk-vue';
1138
+ import { useRouter } from 'vue-router';
1139
+ import { onMounted, ref } from 'vue';
1140
+ import type { EmbeddedFlow } from '@strivacity/sdk-vue';
1141
+
1142
+ const { entry } = useStrivacity<EmbeddedFlow>();
1143
+ const router = useRouter();
1144
+
1145
+ const sessionId = ref<string | null>(null);
1146
+ const shortAppId = ref<string | null>(null);
1147
+ const language = ref<string | null>(null);
1148
+
1149
+ onMounted(async () => {
1150
+ try {
1151
+ const data = await entry();
1152
+
1153
+ // Set properties for sty-login component
1154
+ sessionId.value = data.session_id;
1155
+ shortAppId.value = data.short_app_id;
1156
+ language.value = data.language;
1157
+ } catch (error) {
1158
+ await router.push(`/error?message=${encodeURIComponent(error.message)}`);
1159
+ }
1160
+ });
1161
+
1162
+ async function onLogin() {
1163
+ // User authenticated - navigate to a protected page
1164
+ await router.push('/profile');
1165
+ }
1166
+
1167
+ async function onError(event: CustomEvent) {
1168
+ await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
1169
+ }
1170
+ </script>
1171
+
1172
+ <template>
1173
+ <section v-if="sessionId">
1174
+ <sty-notifications></sty-notifications>
1175
+ <sty-login
1176
+ :sessionId="sessionId"
1177
+ :shortAppId="shortAppId"
1178
+ :lang="language"
1179
+ @login="onLogin"
1180
+ @error="onError"
1181
+ ></sty-login>
1182
+ <sty-language-selector></sty-language-selector>
1183
+ </section>
1184
+ <section v-else>
1185
+ <h1>Loading...</h1>
1186
+ </section>
1187
+ </template>
1188
+ ```
1189
+
1190
+ #### Logout
1191
+
1192
+ 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`.
1193
+
1194
+ ```vue
1195
+ <!-- pages/Logout.vue -->
1196
+ <script setup lang="ts">
1197
+ import { useStrivacity } from '@strivacity/sdk-vue';
1198
+ import { onMounted } from 'vue';
1199
+
1200
+ const { logout } = useStrivacity();
1201
+
1202
+ onMounted(async () => {
1203
+ await logout();
1204
+ });
1205
+ </script>
1206
+
1207
+ <template>
1208
+ <section>
1209
+ <h1>Logging out...</h1>
1210
+ </section>
1211
+ </template>
1212
+ ```
1213
+
1214
+ > The `postLogoutRedirectUri` must be configured in your Strivacity application client settings as an allowed post-logout redirect URI. If the URL is invalid or not provided, the user remains on the Strivacity-hosted logged out page.
1215
+
1216
+ #### Token management
1217
+
1218
+ Call these methods to manage the session and access token.
1219
+
1220
+ ```vue
1221
+ <script setup lang="ts">
1222
+ import { useStrivacity } from '@strivacity/sdk-vue';
1223
+ import { computed } from 'vue';
1224
+
1225
+ const { idTokenClaims, accessToken, refreshToken, init, refresh, revoke } = useStrivacity();
1226
+
1227
+ // Refresh the access token using the refresh token
1228
+ // (automatically waits for SDK initialization and session loading)
1229
+ await refresh();
1230
+
1231
+ // Revoke all tokens at the authorization server and clear the local session
1232
+ // (automatically waits for SDK initialization and session loading)
1233
+ await revoke();
1234
+
1235
+ // Get the current access and refresh tokens (synchronous property - ensure SDK is initialized first)
1236
+ await init();
1237
+ idTokenClaims.value // read-only property - returns the current ID token claims or null if not authenticated
1238
+ accessToken.value; // read-only property - returns the current access token or null if not authenticated
1239
+ refreshToken.value; // read-only property - returns the current refresh token or null if not authenticated
1240
+ </script>
1241
+ ```
633
1242
 
634
1243
  ---
635
1244
 
636
- **Type: `NativeContext`**
1245
+ ## Composables API
1246
+
1247
+ ### useStrivacity
1248
+
1249
+ The main composable for accessing the SDK instance and authentication state.
1250
+
1251
+ ```ts
1252
+ import { useStrivacity } from '@strivacity/sdk-vue';
1253
+ import type { RedirectFlow } from '@strivacity/sdk-vue';
1254
+
1255
+ const ctx = useStrivacity<RedirectFlow>();
1256
+ ```
1257
+
1258
+ #### Returns
1259
+
1260
+ ```ts
1261
+ {
1262
+ // SDK instance (access any SDK method)
1263
+ sdk: RedirectFlow | PopupFlow | EmbeddedFlow | NativeFlow;
1264
+
1265
+ // Reactive state
1266
+ loading: Ref<boolean>; // True during initialization
1267
+ isAuthenticated: Ref<boolean>; // True if user has valid session
1268
+ idTokenClaims: Ref<IdTokenClaims | null>; // Decoded ID token claims
1269
+ accessToken: Ref<string | null>; // Current access token
1270
+ refreshToken: Ref<string | null>; // Current refresh token
1271
+
1272
+ // Methods (all are async)
1273
+ login(params?: LoginParams): Promise<void>; // Start login flow
1274
+ register(params?: LoginParams): Promise<void>; // Start registration flow
1275
+ handleCallback(url?: string): Promise<void>; // Handle OAuth callback
1276
+ logout(params?: LogoutParams): Promise<void>; // End session
1277
+ refresh(): Promise<void>; // Refresh access token
1278
+ revoke(): Promise<void>; // Revoke tokens
1279
+ entry(): Promise<EntryData>; // Handle external entry (embedded/native only)
1280
+ }
1281
+ ```
637
1282
 
638
- - **`login(options?: LoginOptions): Promise<NativeFlowHandler>`**: Initiates login using the native flow.
639
- - **`register(options?: RegisterOptions): Promise<NativeFlowHandler>`**: Initiates registration using the native flow.
640
- - **`refresh(): Promise<void>`**: Refreshes the user's session.
641
- - **`revoke(): Promise<void>`**: Revokes the current session tokens.
642
- - **`logout(options?: LogoutOptions): Promise<void>`**: Logs the user out via redirect.
643
- - **`handleCallback(url?: string): Promise<void>`**: Processes the authorization callback.
644
- - **`entry(): Promise<Record<string, string>>`**: Processes an externally-initiated flow URL and returns the parameters needed to resume the flow.
1283
+ ### useNativeLogin
1284
+
1285
+ Composable for managing native login flow state. Only available in `native` mode.
1286
+
1287
+ ```ts
1288
+ import { useNativeLogin } from '@strivacity/sdk-vue';
1289
+ import type { NativeParams } from '@strivacity/sdk-vue';
1290
+
1291
+ const ctx = useNativeLogin({
1292
+ params: { /* login params */ },
1293
+ onLogin: (session) => { /* handle login */ },
1294
+ onError: (error) => { /* handle error */ },
1295
+ // ... other callbacks
1296
+ });
1297
+ ```
1298
+
1299
+ #### Options
1300
+
1301
+ ```ts
1302
+ {
1303
+ params?: NativeParams; // Initial flow parameters
1304
+ onLogin?: (session: Session) => void; // Called on successful login
1305
+ onClose?: () => void; // Called when user closes the flow
1306
+ onError?: (error: unknown) => void; // Called on error
1307
+ onFallback?: (error: FallbackError) => void; // Called when fallback needed
1308
+ onGlobalMessage?: (message: NativeFlowMessage) => void; // Called for global messages
1309
+ }
1310
+ ```
1311
+
1312
+ #### Returns
1313
+
1314
+ ```ts
1315
+ {
1316
+ // Reactive state
1317
+ loading: Ref<boolean>; // True while fetching next screen
1318
+ state: Ref<NativeFlowState>; // Current flow state (screen, forms, layout, etc.)
1319
+ forms: Ref<Record<string, Record<string, unknown>>>; // Form data by form ID
1320
+ messages: Ref<Record<string, Record<string, NativeFlowMessage>>>; // Validation messages
1321
+
1322
+ // Methods
1323
+ submitForm(formId: string): Promise<void>; // Submit a form and advance to next screen
1324
+ triggerFallback(message?: string): void; // Manually trigger fallback to hosted journey
1325
+ }
1326
+ ```
645
1327
 
646
1328
  ---
647
1329
 
648
- ### `StyLoginRenderer` component
1330
+ ## Route guards
649
1331
 
650
- Used in `native` mode to render the authentication UI with your own widget components.
1332
+ Protect routes that require authentication using Vue Router's navigation guards:
651
1333
 
652
- **Props**
1334
+ ```ts
1335
+ // router.ts
1336
+ import { createRouter, createWebHistory } from 'vue-router';
1337
+ import { useStrivacity } from '@strivacity/sdk-vue';
653
1338
 
654
- - **`params?: NativeParams`**: Additional parameters for the native login flow.
655
- - **`widgets?: PartialRecord<WidgetType, Vue.Component>`**: Custom Vue components for each widget type used in the flow.
656
- - **`sessionId?: string | null`**: Session ID for resuming an existing authentication session. Typically extracted from URL parameters when returning from an external identity provider.
657
- - **`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.
1339
+ const router = createRouter({
1340
+ history: createWebHistory(),
1341
+ routes: [
1342
+ {
1343
+ path: '/profile',
1344
+ component: Profile,
1345
+ beforeEnter: async () => {
1346
+ const { sdk, isAuthenticated } = useStrivacity();
1347
+ await sdk.init();
1348
+
1349
+ if (!isAuthenticated.value) {
1350
+ return '/login';
1351
+ }
1352
+ },
1353
+ },
1354
+ ],
1355
+ });
1356
+ ```
658
1357
 
659
- **Events**
1358
+ ---
660
1359
 
661
- - **`@login`**: Emitted on successful authentication. Receives `IdTokenClaims | null`.
662
- - **`@fallback`**: Emitted when the native flow needs to fall back to redirect. Receives `FallbackError` with a fallback URL.
663
- - **`@error`**: Emitted when an error occurs during authentication.
664
- - **`@global-message`**: Emitted when the flow wants to display a global message (e.g. account lockout warning).
665
- - **`@block-ready`**: Emitted on flow state transitions. Receives `{ previousState: LoginFlowState; state: LoginFlowState }`. Useful for analytics and custom logging.
666
- - **`@update:language`**: Emitted after the session starts with the resolved language string. Used automatically by `v-model:language`.
1360
+ ## Shared features
667
1361
 
668
- ## Vulnerability Reporting
1362
+ The Vue SDK is built on top of the core SDK and supports all its features:
669
1363
 
670
- 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.
1364
+ - **[Storages](../sdk-core/README.md#storages)** - localStorage, sessionStorage, IndexedDB, Cache API, Memory, Worker
1365
+ - **[SDK events](../sdk-core/README.md#sdk-events)** - Subscribe to authentication lifecycle events
1366
+ - **[Logging](../sdk-core/README.md#logging)** - Built-in and custom logger support
1367
+ - **[HTTP client](../sdk-core/README.md#http-client)** - Custom HTTP client integration
1368
+ - **[Error handling](../sdk-core/README.md#error-handling)** - Typed error classes for different failure scenarios
1369
+ - **[Utility functions](../sdk-core/README.md#utility-functions)** - Base64URL, JWT decoding, encryption, etc.
1370
+ - **[Caching](../sdk-core/README.md#caching)** - OIDC metadata and JWKS caching
671
1371
 
672
- ## License
1372
+ ---
673
1373
 
674
- @strivacity/sdk-vue is available under the MIT License. See the [LICENSE](https://github.com/Strivacity/sdk-js/blob/main/LICENSE) file for more info.
1374
+ ## Configuration reference
675
1375
 
676
- ## Contributing
1376
+ The Vue SDK accepts the same configuration as the core SDK. For detailed information about each option, see the [core SDK configuration reference](../sdk-core/README.md#configuration-reference).
677
1377
 
678
- Please see our [contributing guide](https://github.com/Strivacity/sdk-js/blob/main/CONTRIBUTING.md).
1378
+ ---
1379
+
1380
+ ## Advanced
1381
+
1382
+ ### Ionic / Capacitor support
1383
+
1384
+ The SDK works with Ionic and Capacitor applications. For native mobile platforms (iOS/Android), you need to override the default `urlHandler`, `callbackHandler`, and `storage` implementations to integrate with Capacitor plugins.
1385
+
1386
+ ```ts
1387
+ // main.ts
1388
+ import { createApp } from 'vue';
1389
+ import { createStrivacitySDK, SDKStorage, SDKHttpClient } from '@strivacity/sdk-vue';
1390
+ import { Capacitor, CapacitorHttp } from '@capacitor/core';
1391
+ import { Preferences } from '@capacitor/preferences';
1392
+ import { InAppBrowser } from '@capacitor/inappbrowser';
1393
+ import { redirectUrlHandler, redirectCallbackHandler } from '@strivacity/sdk-core/utils/handlers';
1394
+ import type { PluginListenerHandle } from '@capacitor/core';
1395
+ import type { HttpClientResponse } from '@strivacity/sdk-vue';
1396
+ import App from './App.vue';
1397
+
1398
+ // Custom HTTP client using Capacitor's HTTP plugin
1399
+ class CapacitorHttpClient extends SDKHttpClient {
1400
+ async request<T>(url: string, options?: RequestInit): Promise<HttpClientResponse<T>> {
1401
+ const response = await CapacitorHttp.request({
1402
+ url,
1403
+ method: options?.method || 'GET',
1404
+ headers: (options?.headers as Record<string, string>) || {},
1405
+ data: options?.body,
1406
+ webFetchExtra: options,
1407
+ });
1408
+
1409
+ return {
1410
+ headers: new Headers(response.headers),
1411
+ ok: response.status >= 200 && response.status < 300,
1412
+ status: response.status,
1413
+ statusText: '',
1414
+ url: response.url,
1415
+ json: () => Promise.resolve(response.data),
1416
+ text: () => Promise.resolve(response.data),
1417
+ };
1418
+ }
1419
+ }
1420
+
1421
+ // Custom storage using Capacitor Preferences
1422
+ class CapacitorStorage extends SDKStorage {
1423
+ async get(key: string): Promise<string | null> {
1424
+ const { value } = await Preferences.get({ key });
1425
+ return value;
1426
+ }
1427
+
1428
+ async set(key: string, value: string): Promise<void> {
1429
+ await Preferences.set({ key, value });
1430
+ }
1431
+
1432
+ async delete(key: string): Promise<void> {
1433
+ await Preferences.remove({ key });
1434
+ }
1435
+ }
1436
+
1437
+ const app = createApp(App);
1438
+
1439
+ app.use(
1440
+ createStrivacitySDK({
1441
+ mode: 'redirect',
1442
+ issuer: 'https://<YOUR_TENANT_DOMAIN>',
1443
+ clientId: 'YOUR_CLIENT_ID',
1444
+ redirectUri: 'https://your-app.example.com/callback',
1445
+ scopes: ['openid', 'profile', 'email'],
1446
+
1447
+ // Use Capacitor HTTP on native platforms
1448
+ httpClient: CapacitorHttpClient,
1449
+
1450
+ // Use Capacitor Preferences on native platforms, localStorage on web
1451
+ storage: Capacitor.getPlatform() === 'web' ? LocalStorage : CapacitorStorage,
1452
+
1453
+ // Handle URL redirects with InAppBrowser on native platforms
1454
+ async urlHandler(url, responseMode) {
1455
+ if (Capacitor.getPlatform() === 'web') {
1456
+ return redirectUrlHandler(url, responseMode);
1457
+ } else {
1458
+ await InAppBrowser.openInWebView({
1459
+ url,
1460
+ options: { /* Configure InAppBrowser options */ },
1461
+ });
1462
+ }
1463
+ },
1464
+
1465
+ // Handle OAuth callback with InAppBrowser listeners on native platforms
1466
+ async callbackHandler(url, responseMode) {
1467
+ if (Capacitor.getPlatform() === 'web') {
1468
+ return redirectCallbackHandler(url, responseMode);
1469
+ }
1470
+
1471
+ return new Promise(async (resolve, reject) => {
1472
+ let navigationListener: PluginListenerHandle | null = null;
1473
+ let finishListener: PluginListenerHandle | null = null;
1474
+ let userCancelled = true;
1475
+
1476
+ const cleanupListeners = async () => {
1477
+ if (navigationListener) {
1478
+ await navigationListener.remove();
1479
+ navigationListener = null;
1480
+ }
1481
+ if (finishListener) {
1482
+ await finishListener.remove();
1483
+ finishListener = null;
1484
+ }
1485
+ };
1486
+
1487
+ try {
1488
+ // Listen for page navigation in InAppBrowser
1489
+ navigationListener = await InAppBrowser.addListener(
1490
+ 'browserPageNavigationCompleted',
1491
+ async (event) => {
1492
+ const navigatedUrl = event.url;
1493
+
1494
+ // Check if the navigated URL matches our callback URL
1495
+ if (navigatedUrl && navigatedUrl.startsWith(url)) {
1496
+ try {
1497
+ const urlInstance = new URL(navigatedUrl);
1498
+ const dataString = responseMode === 'query'
1499
+ ? urlInstance.search
1500
+ : urlInstance.hash;
1501
+ const params = Object.fromEntries(
1502
+ new URLSearchParams(dataString.slice(1))
1503
+ );
1504
+
1505
+ userCancelled = false;
1506
+ await InAppBrowser.close();
1507
+ resolve(params);
1508
+ } catch (error) {
1509
+ await InAppBrowser.close();
1510
+ reject(error);
1511
+ }
1512
+ }
1513
+ }
1514
+ );
1515
+
1516
+ // Listen for browser close event
1517
+ finishListener = await InAppBrowser.addListener('browserClosed', async () => {
1518
+ await cleanupListeners();
1519
+
1520
+ if (userCancelled) {
1521
+ reject(new Error('InAppBrowser flow cancelled by user.'));
1522
+ }
1523
+ });
1524
+ } catch (error) {
1525
+ await cleanupListeners();
1526
+ reject(error);
1527
+ }
1528
+ });
1529
+ },
1530
+ }),
1531
+ );
1532
+
1533
+ app.mount('#app');
1534
+ ```
1535
+
1536
+ > For more details on custom handlers and storage implementations, see the [core SDK advanced section](../sdk-core/README.md#advanced).
1537
+
1538
+ ### Server-side session (BFF) mode
1539
+
1540
+ Set `serverSessionUri` to route login through your own server instead of talking to the identity provider directly from the browser - tokens are then stored server-side and never reach client-side JavaScript. See [Server-side session management](../sdk-core/README.md#server-side-session-management) in the core SDK README for the full explanation and how to read the session on the client.
1541
+
1542
+ ```ts
1543
+ // main.ts
1544
+ app.use(
1545
+ createStrivacitySDK({
1546
+ mode: 'redirect',
1547
+ issuer: 'https://<YOUR_TENANT_DOMAIN>',
1548
+ clientId: 'YOUR_CLIENT_ID',
1549
+ redirectUri: 'https://your-app.example.com/callback',
1550
+ scopes: ['openid', 'profile', 'email'],
1551
+ serverSessionUri: '/api/auth/login', // Requests are routed through your server; tokens are NOT written to client storage
1552
+ }),
1553
+ );
1554
+ ```
1555
+
1556
+ See [`apps/vue`](../../apps/vue) paired with [`apps/backend`](../../apps/backend) for a working example. See also [Using the backend app with SPA apps](../../README.md#using-the-backend-app-with-spa-apps) in the root README.
1557
+
1558
+ ---
1559
+
1560
+ ## Migration guide
1561
+
1562
+ ### Migrating to v4.0
1563
+
1564
+ v4 replaces the SDK's class-based flow architecture with function-based architecture, and adds first-class support for server-managed (BFF) sessions. `createStrivacitySDK()`, `useStrivacity()`, `useNativeLogin()`, and the built-in `redirect`/`popup`/`embedded`/`native` modes are unchanged - only apps that used `mode: 'custom'` or drove `native` mode through the old `NativeFlowHandler` need to update their code.
1565
+
1566
+ #### Class-based flows replaced by functions
1567
+
1568
+ 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:
1569
+
1570
+ ```ts
1571
+ // v3
1572
+ import { NativeFlow } from '@strivacity/sdk-core/flows/NativeFlow';
1573
+
1574
+ export class CustomNativeFlow extends NativeFlow {
1575
+ override async refresh(): Promise<void> {
1576
+ // ...
1577
+ }
1578
+ }
1579
+ ```
1580
+
1581
+ 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:
1582
+
1583
+ ```ts
1584
+ // v4
1585
+ import { createBaseFlow } from '@strivacity/sdk-core/flows/base';
1586
+ import { getDefaultFlowState, getSDKOptions } from '@strivacity/sdk-core/utils';
1587
+ import type { SDKInitConfig, SDKOptions } from '@strivacity/sdk-core/types';
1588
+
1589
+ export function createCustomFlow(initConfig: SDKInitConfig) {
1590
+ const state = getDefaultFlowState();
1591
+ const options = getSDKOptions<SDKOptions>(state, initConfig);
1592
+ const base = createBaseFlow(state, options);
1593
+
1594
+ async function refresh(): Promise<void> {
1595
+ // ...
1596
+ }
1597
+
1598
+ return { ...base, refresh };
1599
+ }
1600
+ ```
1601
+
1602
+ 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 options object passed to `createStrivacitySDK()` - see [Custom flow](../sdk-core/README.md#custom-flow) in the core SDK README for the full pattern and usage example.
1603
+
1604
+ #### Server-managed sessions (BFF) are now built in
1605
+
1606
+ In v3, routing authentication through your own backend meant writing a custom flow class like the one above yourself: manually calling `fetch()` against hand-written endpoints, and reimplementing PKCE/state handling, CSRF protection, and server-side token storage on your own.
1607
+
1608
+ `@strivacity/sdk-vue` doesn't ship its own server - it's a client SDK for SPAs, meant to pair with a separate backend. v4 replaces the old hand-rolled approach with a single option, `serverSessionUri`, on `createStrivacitySDK()`, paired with the Server SDK (`createBaseServerSDK` from `@strivacity/sdk-core/server`) running on that backend - PKCE, state, and session storage are all handled by the Server SDK:
1609
+
1610
+ ```ts
1611
+ // v4
1612
+ app.use(
1613
+ createStrivacitySDK({
1614
+ // ...
1615
+ serverSessionUri: '/api/auth/login', // requests are routed through your server; tokens are never written to client storage
1616
+ }),
1617
+ );
1618
+ ```
1619
+
1620
+ See [`apps/vue`](../../apps/vue) paired with [`apps/backend`](../../apps/backend) for a working example.
1621
+
1622
+ #### Native mode: no more `NativeFlowHandler`
1623
+
1624
+ In v3, `native` mode's `login()`/`register()` returned a separate `NativeFlowHandler` instance, and the flow was driven through that handler:
1625
+
1626
+ ```ts
1627
+ // v3
1628
+ const handler = await sdk.login();
1629
+ const state = await handler.startSession(sessionId);
1630
+ const nextState = await handler.submitForm('formId', { identifier: 'user@example.com' });
1631
+ await handler.finalizeSession(nextState.finalizeUrl);
1632
+ ```
1633
+
1634
+ v4 moves `startSession()`, `submitForm()`, and `finalizeSession()` directly onto the flow itself - in `@strivacity/sdk-vue` this is wrapped for you by the [`useNativeLogin()`](#usenativelogin) composable:
1635
+
1636
+ ```ts
1637
+ import { useNativeLogin } from '@strivacity/sdk-vue';
1638
+
1639
+ // v4
1640
+ const { state, forms, messages, submitForm } = useNativeLogin({
1641
+ params: { sessionId },
1642
+ });
1643
+
1644
+ await submitForm('formId');
1645
+ ```
1646
+
1647
+ Update any code that calls `login()`/`register()` and drives the returned handler in `native` mode to use `useNativeLogin()` instead.
679
1648
 
680
1649
  ## Migrating to v3.0
681
1650
 
682
1651
  ### Entry API Major Changes
683
1652
 
684
1653
  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.
1654
+
1655
+ ---
1656
+
1657
+ ## Vulnerability Reporting
1658
+
1659
+ 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.
1660
+
1661
+ ## License
1662
+
1663
+ This package is available under the MIT License. See the [LICENSE](https://github.com/Strivacity/sdk-js/blob/main/LICENSE) file for more info.
1664
+
1665
+ ## Contributing
1666
+
1667
+ Please see our [contributing guide](https://github.com/Strivacity/sdk-js/blob/main/CONTRIBUTING.md).