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