@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.
- package/CHANGELOG.md +14 -18
- package/README.md +1402 -419
- package/dist/composables.cjs +2 -2
- package/dist/composables.cjs.map +1 -1
- package/dist/composables.d.ts +23 -8
- package/dist/composables.mjs +2 -2
- package/dist/composables.mjs.map +1 -1
- package/dist/errors.cjs +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.mjs +1 -0
- package/dist/index.cjs +1 -2
- package/dist/index.d.ts +9 -31
- package/dist/index.mjs +1 -2
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.mjs +2 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/storages.cjs +1 -0
- package/dist/storages.d.ts +1 -0
- package/dist/storages.mjs +1 -0
- package/dist/types.cjs +1 -2
- package/dist/types.d.ts +106 -118
- package/dist/types.mjs +1 -2
- package/dist/utils.cjs +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.mjs +1 -0
- package/eslint.config.mjs +3 -0
- package/package.json +45 -8
- package/project.json +46 -0
- package/src/composables.ts +219 -0
- package/src/env.d.ts +8 -0
- package/src/errors.ts +1 -0
- package/src/index.ts +9 -0
- package/src/plugin.ts +89 -0
- package/src/storages.ts +1 -0
- package/src/types.ts +220 -0
- package/src/utils.ts +1 -0
- package/testing/tests/composables.spec.ts +328 -0
- package/testing/tests/errors.spec.ts +10 -0
- package/testing/tests/index.spec.ts +105 -0
- package/testing/tests/plugin.spec.ts +129 -0
- package/testing/tests/storages.spec.ts +10 -0
- package/testing/tests/utils.spec.ts +10 -0
- package/testing/utils/common.ts +53 -0
- package/tsconfig.app.json +4 -0
- package/tsconfig.json +3 -0
- package/vite.config.mts +53 -0
- package/dist/assets/login-renderer.vue_vue_type_script_setup_true_lang.cjs +0 -2
- package/dist/assets/login-renderer.vue_vue_type_script_setup_true_lang.cjs.map +0 -1
- package/dist/assets/login-renderer.vue_vue_type_script_setup_true_lang.mjs +0 -2
- package/dist/assets/login-renderer.vue_vue_type_script_setup_true_lang.mjs.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/login-renderer.cjs +0 -2
- package/dist/login-renderer.cjs.map +0 -1
- package/dist/login-renderer.mjs +0 -2
- package/dist/login-renderer.mjs.map +0 -1
- package/dist/login-renderer.vue.d.ts +0 -37
- package/dist/types.cjs.map +0 -1
- package/dist/types.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,106 +1,206 @@
|
|
|
1
1
|
# @strivacity/sdk-vue
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
- [Ionic Example app](https://github.com/Strivacity/sdk-js/tree/main/apps/ionic-vue)
|
|
37
|
+
## Prerequisites
|
|
15
38
|
|
|
16
|
-
|
|
39
|
+
- Vue 3.x
|
|
40
|
+
- A Strivacity tenant with an application configured (issuer URL, client ID, redirect URI)
|
|
17
41
|
|
|
18
|
-
|
|
42
|
+
---
|
|
19
43
|
|
|
20
|
-
##
|
|
44
|
+
## Installation
|
|
21
45
|
|
|
22
46
|
```bash
|
|
23
47
|
npm install @strivacity/sdk-vue
|
|
24
48
|
```
|
|
25
49
|
|
|
26
|
-
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Choosing a mode
|
|
53
|
+
|
|
54
|
+
The SDK supports **four authentication modes**:
|
|
27
55
|
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
147
|
+
#### Login
|
|
63
148
|
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
####
|
|
179
|
+
#### Handle the callback
|
|
86
180
|
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
244
|
+
#### Logout
|
|
154
245
|
|
|
155
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
164
|
-
const { isAuthenticated, logout } = useStrivacity();
|
|
254
|
+
const { logout } = useStrivacity();
|
|
165
255
|
|
|
166
256
|
onMounted(async () => {
|
|
167
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
297
|
+
---
|
|
206
298
|
|
|
207
|
-
|
|
299
|
+
### popup mode
|
|
208
300
|
|
|
209
|
-
|
|
301
|
+
> For details on how this mode works, see the [hosted journey documentation](https://docs.strivacity.com/reference/hosted-journey).
|
|
210
302
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
import
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
335
|
+
import { onMounted } from 'vue';
|
|
253
336
|
import { useRouter } from 'vue-router';
|
|
254
|
-
import {
|
|
255
|
-
import {
|
|
337
|
+
import { useStrivacity } from '@strivacity/sdk-vue';
|
|
338
|
+
import type { PopupFlow } from '@strivacity/sdk-vue';
|
|
256
339
|
|
|
257
340
|
const router = useRouter();
|
|
258
|
-
const
|
|
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
|
-
|
|
274
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
<
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
####
|
|
378
|
+
#### Handle the callback
|
|
314
379
|
|
|
315
|
-
|
|
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
|
-
|
|
319
|
-
|
|
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 {
|
|
395
|
+
const { register } = useStrivacity<PopupFlow>();
|
|
326
396
|
|
|
327
397
|
onMounted(async () => {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
|
346
|
-
<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
|
-
####
|
|
432
|
+
#### Logout
|
|
359
433
|
|
|
360
|
-
|
|
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
|
-
|
|
364
|
-
|
|
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
|
|
369
|
-
const { entry } = useStrivacity();
|
|
442
|
+
const { logout } = useStrivacity();
|
|
370
443
|
|
|
371
444
|
onMounted(async () => {
|
|
372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
460
|
+
Call these methods to manage the session and access token.
|
|
391
461
|
|
|
392
|
-
|
|
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
|
-
|
|
487
|
+
### embedded mode
|
|
395
488
|
|
|
396
|
-
|
|
489
|
+
> For details on how this mode works, see the [embedded journey documentation](https://docs.strivacity.com/reference/embedded-journey).
|
|
397
490
|
|
|
398
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
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(
|
|
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
|
|
517
|
+
#### Login / Register
|
|
421
518
|
|
|
422
|
-
|
|
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
|
|
432
|
-
|
|
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
|
-
|
|
435
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
};
|
|
448
|
-
|
|
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
|
-
<
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
Same as the callback page example in native mode.
|
|
572
|
+
##### Controlling when the flow starts
|
|
463
573
|
|
|
464
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
596
|
+
async function onLogin() {
|
|
597
|
+
await router.push('/profile');
|
|
598
|
+
}
|
|
471
599
|
|
|
472
|
-
|
|
600
|
+
async function onError(event: CustomEvent) {
|
|
601
|
+
await router.push(`/error?message=${encodeURIComponent(event.detail)}`);
|
|
602
|
+
}
|
|
603
|
+
</script>
|
|
473
604
|
|
|
474
|
-
|
|
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
|
-
|
|
615
|
+
You can also set params via the `params` property before calling `start()`:
|
|
477
616
|
|
|
478
|
-
|
|
617
|
+
```vue
|
|
618
|
+
<script setup lang="ts">
|
|
619
|
+
import { useTemplateRef } from 'vue';
|
|
620
|
+
import type { LoginComponent } from '@strivacity/sdk-vue/types';
|
|
479
621
|
|
|
480
|
-
|
|
622
|
+
const loginEl = useTemplateRef<LoginComponent>('loginEl');
|
|
481
623
|
|
|
482
|
-
|
|
624
|
+
async function onStartClick() {
|
|
625
|
+
if (!loginEl.value) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
483
628
|
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
646
|
+
##### Login events
|
|
498
647
|
|
|
499
|
-
|
|
648
|
+
The `<sty-login>` element dispatches `login`, `close`, and `error` custom events. Listen to them using Vue's `@` directive:
|
|
500
649
|
|
|
501
|
-
|
|
650
|
+
```vue
|
|
651
|
+
<!-- pages/Login.vue -->
|
|
652
|
+
<script setup lang="ts">
|
|
653
|
+
import { useRouter } from 'vue-router';
|
|
502
654
|
|
|
503
|
-
|
|
504
|
-
import type { SDKLogging } from '@strivacity/sdk-vue';
|
|
655
|
+
const router = useRouter();
|
|
505
656
|
|
|
506
|
-
|
|
507
|
-
|
|
657
|
+
async function onLogin() {
|
|
658
|
+
// User authenticated - navigate to a protected page
|
|
659
|
+
await router.push('/profile');
|
|
660
|
+
}
|
|
508
661
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
662
|
+
function onClose() {
|
|
663
|
+
// User cancelled or closed the login flow
|
|
664
|
+
window.location.reload();
|
|
665
|
+
}
|
|
512
666
|
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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
|
-
|
|
522
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
707
|
+
onMounted(() => {
|
|
708
|
+
document.addEventListener('notification', onNotification);
|
|
709
|
+
});
|
|
530
710
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
logging: MyLogger,
|
|
711
|
+
onUnmounted(() => {
|
|
712
|
+
document.removeEventListener('notification', onNotification);
|
|
534
713
|
});
|
|
714
|
+
</script>
|
|
535
715
|
```
|
|
536
716
|
|
|
537
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
546
|
-
|
|
721
|
+
```vue
|
|
722
|
+
<script setup lang="ts">
|
|
723
|
+
import { ref, useTemplateRef } from 'vue';
|
|
547
724
|
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
728
|
+
function changeLanguage(lang: string) {
|
|
729
|
+
currentLang.value = lang;
|
|
559
730
|
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
### CORS configuration
|
|
749
|
+
#### Handle the callback
|
|
581
750
|
|
|
582
|
-
|
|
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
|
-
|
|
753
|
+
#### Externally-initiated flows (entry)
|
|
589
754
|
|
|
590
|
-
|
|
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
|
-
|
|
593
|
-
useStrivacity<T extends PopupContext | RedirectContext | NativeContext>(): T;
|
|
594
|
-
```
|
|
757
|
+
You have two options:
|
|
595
758
|
|
|
596
|
-
|
|
759
|
+
**Option 1: Redirect to a separate login page**
|
|
597
760
|
|
|
598
|
-
|
|
761
|
+
Forward the parameters as query params to your login page:
|
|
599
762
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
774
|
+
onMounted(async () => {
|
|
775
|
+
try {
|
|
776
|
+
const data = await entry();
|
|
613
777
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
1330
|
+
## Route guards
|
|
649
1331
|
|
|
650
|
-
|
|
1332
|
+
Protect routes that require authentication using Vue Router's navigation guards:
|
|
651
1333
|
|
|
652
|
-
|
|
1334
|
+
```ts
|
|
1335
|
+
// router.ts
|
|
1336
|
+
import { createRouter, createWebHistory } from 'vue-router';
|
|
1337
|
+
import { useStrivacity } from '@strivacity/sdk-vue';
|
|
653
1338
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
1358
|
+
---
|
|
660
1359
|
|
|
661
|
-
|
|
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
|
-
|
|
1362
|
+
The Vue SDK is built on top of the core SDK and supports all its features:
|
|
669
1363
|
|
|
670
|
-
|
|
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
|
-
|
|
1372
|
+
---
|
|
673
1373
|
|
|
674
|
-
|
|
1374
|
+
## Configuration reference
|
|
675
1375
|
|
|
676
|
-
|
|
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
|
-
|
|
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).
|