@strivacity/sdk-svelte 3.0.2 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1714 -428
  3. package/dist/assets/rolldown-runtime.cjs +1 -0
  4. package/dist/client/components.svelte.cjs +2 -0
  5. package/dist/client/components.svelte.cjs.map +1 -0
  6. package/dist/client/components.svelte.d.ts +11 -0
  7. package/dist/client/components.svelte.mjs +2 -0
  8. package/dist/client/components.svelte.mjs.map +1 -0
  9. package/dist/client/hooks.svelte.cjs +2 -0
  10. package/dist/client/hooks.svelte.cjs.map +1 -0
  11. package/dist/client/hooks.svelte.d.ts +27 -0
  12. package/dist/client/hooks.svelte.mjs +2 -0
  13. package/dist/client/hooks.svelte.mjs.map +1 -0
  14. package/dist/client/index.cjs +1 -0
  15. package/dist/client/index.d.ts +8 -0
  16. package/dist/client/index.mjs +1 -0
  17. package/dist/client/storages.cjs +1 -0
  18. package/dist/client/storages.d.ts +1 -0
  19. package/dist/client/storages.mjs +1 -0
  20. package/dist/client/types.cjs +1 -0
  21. package/dist/client/types.d.ts +238 -0
  22. package/dist/client/types.mjs +1 -0
  23. package/dist/client/utils.cjs +1 -0
  24. package/dist/client/utils.d.ts +1 -0
  25. package/dist/client/utils.mjs +1 -0
  26. package/dist/errors.cjs +1 -0
  27. package/dist/errors.d.ts +1 -0
  28. package/dist/errors.mjs +1 -0
  29. package/dist/server/index.cjs +1 -0
  30. package/dist/server/index.d.ts +6 -0
  31. package/dist/server/index.mjs +1 -0
  32. package/dist/server/sdk.cjs +2 -0
  33. package/dist/server/sdk.cjs.map +1 -0
  34. package/dist/server/sdk.d.ts +8 -0
  35. package/dist/server/sdk.mjs +2 -0
  36. package/dist/server/sdk.mjs.map +1 -0
  37. package/dist/server/storages.cjs +2 -0
  38. package/dist/server/storages.cjs.map +1 -0
  39. package/dist/server/storages.d.ts +10 -0
  40. package/dist/server/storages.mjs +2 -0
  41. package/dist/server/storages.mjs.map +1 -0
  42. package/dist/server/types.cjs +1 -0
  43. package/dist/server/types.d.ts +35 -0
  44. package/dist/server/types.mjs +1 -0
  45. package/dist/server/utils.cjs +1 -0
  46. package/dist/server/utils.d.ts +1 -0
  47. package/dist/server/utils.mjs +1 -0
  48. package/dist/types.cjs +1 -2
  49. package/dist/types.d.ts +2 -181
  50. package/dist/types.mjs +1 -2
  51. package/eslint.config.mjs +3 -0
  52. package/package.json +59 -11
  53. package/project.json +47 -0
  54. package/src/client/components.svelte.ts +125 -0
  55. package/src/client/hooks.svelte.ts +221 -0
  56. package/src/client/index.ts +8 -0
  57. package/src/client/storages.ts +1 -0
  58. package/src/client/types.ts +299 -0
  59. package/src/client/utils.ts +1 -0
  60. package/src/errors.ts +1 -0
  61. package/src/server/index.ts +6 -0
  62. package/src/server/sdk.ts +77 -0
  63. package/src/server/storages.ts +22 -0
  64. package/src/server/types.ts +49 -0
  65. package/src/server/utils.ts +1 -0
  66. package/src/types.ts +2 -0
  67. package/svelte.config.js +5 -0
  68. package/testing/fixtures/AuthProviderHost.svelte +29 -0
  69. package/testing/fixtures/Harness.svelte +30 -0
  70. package/testing/fixtures/NativeLoginConsumer.svelte +21 -0
  71. package/testing/fixtures/NativeLoginContextConsumer.svelte +10 -0
  72. package/testing/fixtures/StrivacityConsumer.svelte +10 -0
  73. package/testing/tests/components.spec.ts +183 -0
  74. package/testing/tests/errors.spec.ts +10 -0
  75. package/testing/tests/hooks.spec.ts +275 -0
  76. package/testing/tests/index.spec.ts +199 -0
  77. package/testing/tests/server/sdk.spec.ts +214 -0
  78. package/testing/tests/server/storages.spec.ts +50 -0
  79. package/testing/tests/server/utils.spec.ts +10 -0
  80. package/testing/tests/storages.spec.ts +10 -0
  81. package/testing/tests/utils.spec.ts +10 -0
  82. package/testing/utils/mount.ts +83 -0
  83. package/tsconfig.app.json +4 -0
  84. package/tsconfig.json +6 -0
  85. package/vite.config.mts +52 -0
  86. package/dist/AuthProvider.cjs +0 -2
  87. package/dist/AuthProvider.cjs.map +0 -1
  88. package/dist/AuthProvider.mjs +0 -2
  89. package/dist/AuthProvider.mjs.map +0 -1
  90. package/dist/AuthProvider.svelte.d.ts +0 -1
  91. package/dist/LoginRenderer.cjs +0 -2
  92. package/dist/LoginRenderer.cjs.map +0 -1
  93. package/dist/LoginRenderer.mjs +0 -2
  94. package/dist/LoginRenderer.mjs.map +0 -1
  95. package/dist/LoginRenderer.svelte.d.ts +0 -1
  96. package/dist/composables.cjs +0 -2
  97. package/dist/composables.cjs.map +0 -1
  98. package/dist/composables.d.ts +0 -12
  99. package/dist/composables.mjs +0 -2
  100. package/dist/composables.mjs.map +0 -1
  101. package/dist/index.cjs +0 -2
  102. package/dist/index.cjs.map +0 -1
  103. package/dist/index.d.ts +0 -13
  104. package/dist/index.mjs +0 -2
  105. package/dist/index.mjs.map +0 -1
  106. package/dist/types.cjs.map +0 -1
  107. package/dist/types.mjs.map +0 -1
package/README.md CHANGED
@@ -1,164 +1,1386 @@
1
1
  # @strivacity/sdk-svelte
2
2
 
3
- A Svelte library that integrates Strivacity's policy-driven authentication journeys into your application using the OAuth 2.0 PKCE flow. Supports `redirect`, `popup`, `native`, and `embedded` modes.
3
+ 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
- See our [Developer Portal](https://www.strivacity.com/learn-support/developer-hub) to get started with developing with the Strivacity product.
43
+ ---
44
+
45
+ ## Prerequisites
46
+
47
+ - 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
- ## Overview
990
+ const ctx = useStrivacity<EmbeddedFlow>();
8
991
 
9
- This SDK allows you to integrate Strivacity's policy-driven journeys into your Svelte application. It wraps the `@strivacity/sdk-core` library as a Svelte context provider and exposes a `useStrivacity` function that provides reactive authentication state and methods throughout your component tree. The SDK uses the OAuth 2.0 PKCE flow to authenticate with Strivacity. For detailed configuration options, available modes, and advanced usage refer to the [`@strivacity/sdk-core` documentation](https://github.com/Strivacity/sdk-js/blob/main/packages/sdk-core/README.md).
992
+ let sessionId = $state<string | null>(null);
993
+ let shortAppId = $state<string | null>(null);
994
+ let language = $state<string | null>(null);
10
995
 
11
- ## Demo Application
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
- - [Example app](https://github.com/Strivacity/sdk-js/tree/main/apps/svelte)
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
- ## Requirements
1028
+ ##### Logout
16
1029
 
17
- - Svelte: 5+
1030
+ **Client-managed sessions**:
18
1031
 
19
- ## Install
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
- ```bash
22
- npm install @strivacity/sdk-svelte
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
- ## Usage
1069
+ ##### Token management
26
1070
 
27
- ### Initialization
1071
+ **Client-managed sessions**:
28
1072
 
29
- Wrap your application with `StyAuthProvider` in your root layout:
1073
+ Call these methods to manage the session and access token client-side.
30
1074
 
31
1075
  ```svelte
32
- <!-- src/routes/+layout.svelte -->
33
- <script>
34
- import { StyAuthProvider } from '@strivacity/sdk-svelte';
35
-
36
- const options = {
37
- mode: 'redirect', // or 'popup', 'native', 'embedded'
38
- issuer: 'https://<YOUR_DOMAIN>',
39
- scopes: ['openid', 'profile'],
40
- clientId: '<YOUR_CLIENT_ID>',
41
- redirectUri: '<YOUR_REDIRECT_URI>',
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
- <StyAuthProvider {options}>
46
- <slot />
47
- </StyAuthProvider>
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
- Use the `useStrivacity` function in any component to access authentication state:
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
- import { useStrivacity } from '@strivacity/sdk-svelte';
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
- const { loading, isAuthenticated, idTokenClaims } = useStrivacity();
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
- ### Redirect / Popup mode
1124
+ ---
1125
+
1126
+ #### native mode
61
1127
 
62
- In `redirect` mode the user is taken to the identity provider in the same window; in `popup` mode authentication happens in a popup. Both are initiated the same way from code.
1128
+ > For details on how this mode works, see the [native journey documentation](https://docs.strivacity.com/reference/native-journey).
63
1129
 
64
- #### Login page example
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 { onMount } from 'svelte';
70
- import { useStrivacity } from '@strivacity/sdk-svelte';
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
- const { login } = useStrivacity();
1221
+ **Server-managed sessions**:
73
1222
 
74
- onMount(() => {
75
- login();
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>Redirecting...</h1>
1270
+ <h1>Loading...</h1>
81
1271
  </section>
82
1272
  ```
83
1273
 
84
- #### Callback page example
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
- The callback page handles the response from the identity provider. It calls `handleCallback()` and redirects to `/profile` on success:
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/callback/+page.svelte -->
90
- <script>
91
- import { onMount } from 'svelte';
92
- import { goto } from '$app/navigation';
93
- import { useStrivacity } from '@strivacity/sdk-svelte';
94
-
95
- const { handleCallback } = useStrivacity();
96
-
97
- onMount(async () => {
98
- try {
99
- await handleCallback();
100
- await goto('/profile');
101
- } catch (error) {
102
- console.error('Error during callback handling:', error);
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
- <section>
108
- <h1>Logging in...</h1>
109
- </section>
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
- #### Profile page example
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/profile/+page.svelte -->
116
- <script>
117
- import { useStrivacity } from '@strivacity/sdk-svelte';
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 { loading, isAuthenticated, accessToken, accessTokenExpired, accessTokenExpirationDate, idTokenClaims, refreshToken } = useStrivacity();
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
- {#if $loading}
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
- #### Logout page example
1372
+ **Server-managed sessions**:
143
1373
 
144
- The `postLogoutRedirectUri` parameter is optional and specifies where users are redirected after logout. This URI must be configured in the Admin Console as an allowed post-logout redirect URI.
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
- import { goto } from '$app/navigation';
151
- import { useStrivacity } from '@strivacity/sdk-svelte';
152
-
153
- const { isAuthenticated, logout } = useStrivacity();
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
- #### Component example
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
- const { isAuthenticated, idTokenClaims, login, logout } = useStrivacity();
1403
+ async function onRefresh() {
1404
+ // Refresh the access token using the refresh token
1405
+ await ctx.refresh();
1406
+ }
176
1407
 
177
- $: name = `${$idTokenClaims?.given_name} ${$idTokenClaims?.family_name}`;
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 $isAuthenticated}
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
- <div>Not logged in</div>
188
- <button on:click={() => login()}>Log in</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
- ### Native mode
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
- In `native` mode the `StyLoginRenderer` component renders the authentication UI inline using your custom widget components. You can define custom components for each input type; see [Example widgets](https://github.com/Strivacity/sdk-js/tree/main/apps/svelte/src/components/widgets).
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
- The example widgets use SCSS for styling and Luxon for date handling:
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
- ```bash
200
- npm install sass luxon
201
- npm install --save-dev @types/luxon
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
- #### Login page example
1446
+ ---
235
1447
 
236
- The login page extracts `session_id` and optionally `language` from the URL on load, cleans up the URL, and passes them to the renderer. When a `session_id` is present the renderer calls `startSession(sessionId)` to resume the existing flow instead of starting a new one. When a `language` parameter is present it is passed to the renderer which uses it for the authentication UI and calls `onlanguagechange` with the resolved language.
1448
+ ### Hooks API
237
1449
 
238
- ```svelte
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
- let sessionId: string | null = null;
246
- let language: string | null = null;
1452
+ The main function for accessing the SDK instance and reactive session state.
247
1453
 
248
- if (window.location.search !== '') {
249
- const url = new URL(window.location.href);
250
- sessionId = url.searchParams.get('session_id');
1454
+ ```ts
1455
+ import { useStrivacity } from '@strivacity/sdk-svelte/client';
1456
+ import type { RedirectFlow } from '@strivacity/sdk-svelte/client';
251
1457
 
252
- if (url.searchParams.has('language')) {
253
- language = url.searchParams.get('language');
254
- }
1458
+ const ctx = useStrivacity<RedirectFlow>();
1459
+ ```
255
1460
 
256
- url.search = '';
257
- history.replaceState({}, '', url.toString());
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
- const onLogin = async () => {
261
- await goto('/profile');
262
- };
1489
+ #### useNativeLogin
263
1490
 
264
- const onFallback = (error: FallbackError) => {
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
- const onError = (error: string) => {
273
- alert(error);
274
- };
1493
+ ```ts
1494
+ import { useNativeLogin } from '@strivacity/sdk-svelte/client';
1495
+ import type { NativeParams } from '@strivacity/sdk-svelte/client';
275
1496
 
276
- const onGlobalMessage = (message: string) => {
277
- alert(message);
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
- const onBlockReady = ({ previousState, state }: { previousState: LoginFlowState; state: LoginFlowState }) => {
281
- console.log('previousState', previousState);
282
- console.log('state', state);
283
- };
284
- </script>
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
- <StyLoginRenderer
287
- {widgets}
288
- {sessionId}
289
- {language}
290
- onlanguagechange={(lang) => (language = lang)}
291
- onlogin={onLogin}
292
- onfallback={({ detail }) => onFallback(detail)}
293
- onerror={({ detail }) => onError(detail)}
294
- onglobalmessage={({ detail }) => onGlobalMessage(detail)}
295
- onblockready={({ detail }) => onBlockReady(detail)}
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
- #### Callback page example
1537
+ ---
300
1538
 
301
- When a `session_id` is present in the URL the native flow is resumed by forwarding it to the login page. Otherwise the standard `handleCallback()` path is used:
1539
+ ## Server SDK
302
1540
 
303
- ```svelte
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
- const query = Object.fromEntries(new URLSearchParams(window.location.search));
311
- const { handleCallback } = useStrivacity();
1543
+ ### Setup
312
1544
 
313
- onMount(async () => {
314
- const url = new URL(location.href);
315
- const sessionId = url.searchParams.get('session_id');
1545
+ ```ts
1546
+ // src/lib/server/strivacity.ts
1547
+ import { createServerSDK } from '@strivacity/sdk-svelte/server';
1548
+ import { sdkOptions } from '../options';
316
1549
 
317
- if (sessionId) {
318
- await goto(`/login?session_id=${sessionId}`);
319
- } else {
320
- try {
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
- </script>
1555
+ ```
329
1556
 
330
- {#if query.error}
331
- <section>
332
- <h1>Error in authentication</h1>
333
- <div>
334
- <h4>{query.error}</h4>
335
- <p>{query.error_description}</p>
336
- </div>
337
- </section>
338
- {:else}
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
- #### Entry page example
1568
+ ### Accessing the session server-side
346
1569
 
347
- The entry page processes flows started by an external process (e.g. password reset) by calling `entry()` to extract the necessary parameters to resume the flow and forwarding them to the callback page:
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
- ```svelte
350
- <!-- src/routes/entry/+page.svelte -->
351
- <script>
352
- import { onMount } from 'svelte';
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 { entry } = useStrivacity();
1577
+ export const load: LayoutServerLoad = async (event) => {
1578
+ return {
1579
+ session: await sdk.getSession(event),
1580
+ };
1581
+ };
1582
+ ```
357
1583
 
358
- onMount(async () => {
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
- if (data && Object.keys(data).length > 0) {
363
- await goto(`/callback?${new URLSearchParams(data).toString()}`);
364
- } else {
365
- await goto('/');
366
- }
367
- } catch (error) {
368
- console.error('Entry failed:', error);
369
- await goto('/');
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
- #### Profile page example
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
- Same as the profile page example in redirect/popup mode.
1600
+ <a id="server-storages"></a>
1601
+ ### Storages
378
1602
 
379
- #### Logout page example
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
- Same as the logout page example in redirect/popup mode.
1605
+ #### Built-in session storages
382
1606
 
383
- ### Embedded mode
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
- In `embedded` mode the `<sty-login>` web component (loaded via `bundle.js` from the cluster) handles rendering. Import the bundle at application startup to register the Strivacity web components:
1610
+ ```ts
1611
+ // src/lib/server/storage.ts
1612
+ import { createSessionIdCookieStorage, createServerMemoryStorage } from '@strivacity/sdk-svelte/server/storages';
386
1613
 
387
- ```svelte
388
- <!-- src/routes/+layout.svelte -->
389
- <script>
390
- import { onMount } from 'svelte';
391
- import { StyAuthProvider } from '@strivacity/sdk-svelte';
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
- onMount(() => {
394
- void import(`${import.meta.env.VITE_ISSUER}/assets/components/bundle.js`);
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
- const options = {
398
- mode: 'embedded',
399
- issuer: 'https://<YOUR_DOMAIN>',
400
- scopes: ['openid', 'profile'],
401
- clientId: '<YOUR_CLIENT_ID>',
402
- redirectUri: '<YOUR_REDIRECT_URI>',
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
- <StyAuthProvider {options}>
407
- <slot />
408
- </StyAuthProvider>
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
- ## Logging
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
- The SDK supports optional logging to help you debug authentication flows and monitor SDK behavior. You can enable the built-in console logger or provide your own custom logger implementation.
1704
+ ### Back-channel logout
414
1705
 
415
- ### Using the Default Logger
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
- Enable the default console logger by adding the `logging` option:
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
- ```svelte
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
- const options = {
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
- <StyAuthProvider {options}>
434
- <slot />
435
- </StyAuthProvider>
1714
+ ```
1715
+ https://your-app.example.com/auth/backchannel-logout
436
1716
  ```
437
1717
 
438
- ### Creating a Custom Logger
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
- Implement the `SDKLogging` interface and pass your class to the `logging` option:
1751
+ ### Server configuration reference
441
1752
 
442
- ```typescript
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
- export class MyLogger implements SDKLogging {
446
- xEventId?: string;
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
- debug(message: string): void {
449
- console.debug(this.xEventId ? `[${this.xEventId}] ${message}` : message);
450
- }
1766
+ ---
451
1767
 
452
- info(message: string): void {
453
- console.info(this.xEventId ? `[${this.xEventId}] ${message}` : message);
454
- }
1768
+ ## Route guards
455
1769
 
456
- warn(message: string): void {
457
- console.warn(this.xEventId ? `[${this.xEventId}] ${message}` : message);
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
- error(message: string, error: Error): void {
461
- console.error(this.xEventId ? `[${this.xEventId}] ${message}` : message, error);
462
- }
463
- }
464
- ```
1773
+ ---
465
1774
 
466
- The `SDKLogging` interface requires `debug`, `info`, `warn`, and `error` methods. The optional `xEventId` property, when set by the SDK, provides a correlation ID to trace related log messages across the authentication flow.
1775
+ ## Shared features
467
1776
 
468
- ## HTTP Client
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
- The SDK uses a built-in `fetch`-based HTTP client for all requests. You can replace it with your own implementation by extending `SDKHttpClient` and passing your class via the `httpClient` option. This is useful when you need to attach custom headers (e.g. `x-sty-app-id`) to every outgoing request or route traffic through a proxy.
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
- ### Adding custom headers to every request
1787
+ ---
473
1788
 
474
- ```svelte
475
- <script lang="ts">
476
- import { StyAuthProvider, SDKHttpClient, type HttpClientResponse, type SDKOptions } from '@strivacity/sdk-svelte';
1789
+ ## Configuration reference
477
1790
 
478
- class CustomHttpClient extends SDKHttpClient {
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
- const options: SDKOptions = {
503
- // ...other options
504
- httpClient: CustomHttpClient,
505
- };
506
- </script>
507
- ```
1793
+ ---
508
1794
 
509
- Any header you add inside `request()` is automatically included in every SDK request
1795
+ ## Migration guide
510
1796
 
511
- ### CORS configuration
1797
+ ### Migrating to v4.0
512
1798
 
513
- For custom request headers to reach the Strivacity cluster, the cluster must be configured to explicitly allow them. Add the header name(s) to the **Access-Control-Allow-Headers** list in the cluster settings. Without this, browsers will block the preflight `OPTIONS` request and the SDK call will fail with a CORS error.
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
- ## API Documentation
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
- ### `useStrivacity` function
1805
+ ```ts
1806
+ // v3
1807
+ import { NativeFlow } from '@strivacity/sdk-core/flows/NativeFlow';
522
1808
 
523
- ```typescript
524
- useStrivacity<T extends PopupContext | RedirectContext | NativeContext>(): T;
1809
+ export class CustomNativeFlow extends NativeFlow {
1810
+ override async refresh(): Promise<void> {
1811
+ // ...
1812
+ }
1813
+ }
525
1814
  ```
526
1815
 
527
- The function returns a different context type depending on the `mode` configured in `StyAuthProvider`.
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
- **Shared properties (all modes)**
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
- - **`sdk: RedirectFlow | PopupFlow | NativeFlow`**: The underlying SDK flow instance.
532
- - **`loading: Readable<boolean>`**: `true` while the session is being initialized.
533
- - **`options: SDKOptions`**: The configured SDK options.
534
- - **`isAuthenticated: Readable<boolean>`**: `true` when the user has a valid session.
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
- **Type: `RedirectContext`**
1833
+ return { ...base, refresh };
1834
+ }
1835
+ ```
544
1836
 
545
- - **`login(options?: LoginOptions): Promise<void>`**: Initiates login by redirecting to the identity provider.
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
- **Type: `PopupContext`**
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
- - **`login(options?: LoginOptions): Promise<void>`**: Initiates login using a popup window.
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
- **Type: `NativeContext`**
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
- - **`login(options?: LoginOptions): Promise<NativeFlowHandler>`**: Initiates login using the native flow.
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
- ### `StyLoginRenderer` component
1878
+ await ctx.submitForm('formId');
1879
+ ```
580
1880
 
581
- Used in `native` mode to render the authentication UI with your own widget components.
1881
+ Update any code that calls `login()`/`register()` and drives the returned handler in `native` mode to use `useNativeLogin()` instead.
582
1882
 
583
- **Props**
1883
+ ## Migrating to v3.0
584
1884
 
585
- - **`params?: NativeParams`**: Additional parameters for the native login flow.
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
- **Events**
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
- - **`onlogin`**: Called on successful authentication. Receives `IdTokenClaims | null`.
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
- @strivacity/sdk-svelte is available under the MIT License. See the [LICENSE](https://github.com/Strivacity/sdk-js/blob/main/LICENSE) file for more info.
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.