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