@strivacity/sdk-angular 4.0.0-beta.1 → 4.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -62
- package/dist/fesm2022/strivacity-sdk-angular.mjs +36 -131
- package/package.json +2 -2
- package/src/lib/services/auth.service.ts +2 -2
- package/src/lib/services/native-login.service.ts +20 -140
- package/src/server/sdk.ts +1 -1
- package/src/server/session.ts +17 -3
- package/src/server/types.ts +3 -3
- package/src/types/index.ts +5 -33
- package/testing/tests/auth.service.spec.ts +1 -1
- package/testing/tests/index.spec.ts +7 -0
- package/testing/tests/native-login.service.spec.ts +10 -14
- package/testing/tests/server/sdk.spec.ts +1 -1
- package/testing/tests/server/session.spec.ts +51 -6
- package/dist/README.md +0 -2217
- package/dist/fesm2022/strivacity-sdk-angular-src-server.mjs +0 -221
- package/dist/fesm2022/strivacity-sdk-angular-src-server.mjs.map +0 -1
- package/dist/fesm2022/strivacity-sdk-angular-src-types.mjs +0 -6
- package/dist/fesm2022/strivacity-sdk-angular-src-types.mjs.map +0 -1
- package/dist/fesm2022/strivacity-sdk-angular.mjs.map +0 -1
- package/dist/types/strivacity-sdk-angular-src-server.d.ts +0 -82
- package/dist/types/strivacity-sdk-angular-src-types.d.ts +0 -41
- package/dist/types/strivacity-sdk-angular.d.ts +0 -147
package/dist/README.md
DELETED
|
@@ -1,2217 +0,0 @@
|
|
|
1
|
-
# @strivacity/sdk-angular
|
|
2
|
-
|
|
3
|
-
Angular SDK for [Strivacity](https://www.strivacity.com) - adds PKCE-protected OIDC authentication to your Angular application. Ships with a client SDK and a backend-for-frontend ([BFF](../../README.md#bff)) Server SDK that mounts as an Express router - 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/angular) - Working Angular 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
|
-
- [Services API](#services-api)
|
|
26
|
-
- [StrivacityAuthService](#strivacityauthservice)
|
|
27
|
-
- [StrivacityNativeLoginService](#strivacitynativeloginservice)
|
|
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)
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## Prerequisites
|
|
46
|
-
|
|
47
|
-
- Angular 20+
|
|
48
|
-
- Express 5+ 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-angular
|
|
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/options.ts
|
|
97
|
-
import type { SDKInitConfig } from '@strivacity/sdk-angular';
|
|
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 router) if you're using client-managed sessions.
|
|
114
|
-
|
|
115
|
-
```ts
|
|
116
|
-
// src/server/strivacity.ts
|
|
117
|
-
import { createServerSDK } from '@strivacity/sdk-angular/server';
|
|
118
|
-
import { sdkOptions } from '../options';
|
|
119
|
-
|
|
120
|
-
export const serverSdk = createServerSDK({
|
|
121
|
-
...sdkOptions,
|
|
122
|
-
secret: process.env.SECRET, // http-only cookie encryption key (random 32+ characters)
|
|
123
|
-
postLoginRedirectUri: '/profile',
|
|
124
|
-
});
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### 3. Mount the Express router
|
|
128
|
-
|
|
129
|
-
`sdk.handlers` is an Express `Router` exposing every auth route - mount it under `authUrlPrefix` in your SSR server entry, ahead of Angular's own request handler:
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
// src/server/server.ts
|
|
133
|
-
import express from 'express';
|
|
134
|
-
import { AngularNodeAppEngine, writeResponseToNodeResponse } from '@angular/ssr/node';
|
|
135
|
-
import { sdkOptions } from '../options';
|
|
136
|
-
import { serverSdk } from './strivacity';
|
|
137
|
-
|
|
138
|
-
const app = express();
|
|
139
|
-
const angularApp = new AngularNodeAppEngine();
|
|
140
|
-
|
|
141
|
-
app.use(sdkOptions.authUrlPrefix!, serverSdk.handlers);
|
|
142
|
-
|
|
143
|
-
app.use((req, res, next) => {
|
|
144
|
-
angularApp
|
|
145
|
-
.handle(req)
|
|
146
|
-
.then((response) => (response ? writeResponseToNodeResponse(response, res) : next()))
|
|
147
|
-
.catch(next);
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Also hydrate the session into Angular's `TransferState` before the app renders, so the client can pick it up without an extra round-trip - add this to your server-only `ApplicationConfig`:
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
// src/server/app.config.server.ts
|
|
155
|
-
import { type ApplicationConfig, mergeApplicationConfig } from '@angular/core';
|
|
156
|
-
import { provideServerRendering } from '@angular/ssr';
|
|
157
|
-
import { provideStrivacityServerSession } from '@strivacity/sdk-angular/server';
|
|
158
|
-
import { appConfig } from '../app/app.config';
|
|
159
|
-
import { serverSdk } from './strivacity';
|
|
160
|
-
|
|
161
|
-
const serverConfig: ApplicationConfig = {
|
|
162
|
-
providers: [provideServerRendering(), provideStrivacityServerSession(serverSdk)],
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
| Method | Path | Description | Response |
|
|
169
|
-
| ------ | --------------------------- | ---------------------------------------------------------- | -------------------------------------------- |
|
|
170
|
-
| `GET` | `/auth/login` | Starts the login flow and redirects to the IDP | `302` redirect to IDP |
|
|
171
|
-
| `GET` | `/auth/register` | Starts the registration flow and redirects to the IDP | `302` redirect to IDP |
|
|
172
|
-
| `GET` | `/auth/callback` | Completes authentication (handles the IDP callback) | `302` redirect or popup close script |
|
|
173
|
-
| `GET` | `/auth/refresh` | Refreshes the access token | `204 No Content` or `302` redirect |
|
|
174
|
-
| `GET` | `/auth/revoke` | Revokes tokens and clears the session | `204 No Content` |
|
|
175
|
-
| `GET` | `/auth/logout` | Ends the session and redirects to the IDP logout page | `302` redirect to IDP logout |
|
|
176
|
-
| `GET` | `/auth/entry` | Handles external flow entry (e.g., password reset link) - embedded/native modes only | JSON with session data |
|
|
177
|
-
| `POST` | `/auth/backchannel-logout` | Processes back-channel logout requests from the IDP | `204 No Content` |
|
|
178
|
-
|
|
179
|
-
> The `/auth` prefix and route names come from `authUrlPrefix` - see [Server configuration reference](#server-configuration-reference).
|
|
180
|
-
|
|
181
|
-
### 4. Provide the SDK
|
|
182
|
-
|
|
183
|
-
```ts
|
|
184
|
-
// src/app/app.config.ts
|
|
185
|
-
import type { ApplicationConfig } from '@angular/core';
|
|
186
|
-
import { provideStrivacity } from '@strivacity/sdk-angular';
|
|
187
|
-
import { sdkOptions } from '../options';
|
|
188
|
-
|
|
189
|
-
export const appConfig: ApplicationConfig = {
|
|
190
|
-
providers: [provideStrivacity(sdkOptions)],
|
|
191
|
-
};
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
For server-managed sessions, nothing else is needed here - `StrivacityAuthService`'s constructor automatically reads the session that `provideStrivacityServerSession` (see step 3) seeded into Angular's `TransferState`, so there's no manual session prop to pass through.
|
|
195
|
-
|
|
196
|
-
> Still bootstrapping via `NgModule`? `StrivacityAuthModule.forRoot(sdkOptions)` is the module-based equivalent of `provideStrivacity(sdkOptions)`.
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## Client SDK
|
|
201
|
-
|
|
202
|
-
### Authentication modes
|
|
203
|
-
|
|
204
|
-
The `mode` set in `src/options.ts` (see [Quick start](#quick-start)) controls which of the four flows below is active - the DI setup shown in the [core SDK docs](../sdk-core/README.md#choosing-a-mode) is already handled by `provideStrivacity()`, so the examples below start directly from the page component level.
|
|
205
|
-
|
|
206
|
-
#### redirect mode
|
|
207
|
-
|
|
208
|
-
> For details on how this mode works, see the [hosted journey documentation](https://docs.strivacity.com/reference/hosted-journey).
|
|
209
|
-
|
|
210
|
-
The current browser tab navigates to the Strivacity-hosted login page and back to the configured `redirectUri` after authentication.
|
|
211
|
-
|
|
212
|
-
##### Login
|
|
213
|
-
|
|
214
|
-
**Client-managed sessions**:
|
|
215
|
-
|
|
216
|
-
Call this to start the login flow. It redirects the user to the Strivacity login page in the current browser tab, where they authenticate.
|
|
217
|
-
|
|
218
|
-
```ts
|
|
219
|
-
// src/app/pages/login/login.page.ts
|
|
220
|
-
import { Component, type OnInit, inject } from '@angular/core';
|
|
221
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
222
|
-
|
|
223
|
-
@Component({
|
|
224
|
-
selector: 'app-login-page',
|
|
225
|
-
template: `
|
|
226
|
-
<section>
|
|
227
|
-
<h1>Redirecting to login...</h1>
|
|
228
|
-
</section>
|
|
229
|
-
`,
|
|
230
|
-
})
|
|
231
|
-
export class LoginPage implements OnInit {
|
|
232
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
233
|
-
|
|
234
|
-
ngOnInit(): void {
|
|
235
|
-
void this.authService.login({
|
|
236
|
-
// Optional parameters
|
|
237
|
-
loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
|
|
238
|
-
acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
|
|
239
|
-
audiences: ['https://api.example.com'], // target resources for the access token
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
**Server-managed sessions**:
|
|
246
|
-
|
|
247
|
-
Skip the client SDK entirely and redirect straight to `/auth/login` - the Express router from [Quick start](#quick-start) intercepts the request and the Server SDK builds the authorization request and redirects to the IDP:
|
|
248
|
-
|
|
249
|
-
```ts
|
|
250
|
-
// src/app/pages/login/login.page.ts
|
|
251
|
-
import { Component, type OnInit, PLATFORM_ID, RESPONSE_INIT, inject } from '@angular/core';
|
|
252
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
253
|
-
|
|
254
|
-
@Component({
|
|
255
|
-
selector: 'app-login-page',
|
|
256
|
-
template: '',
|
|
257
|
-
})
|
|
258
|
-
export class LoginPage implements OnInit {
|
|
259
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
260
|
-
private readonly responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
261
|
-
|
|
262
|
-
ngOnInit(): void {
|
|
263
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
264
|
-
globalThis.location.href = '/auth/login';
|
|
265
|
-
} else if (this.responseInit) {
|
|
266
|
-
// Issues a real HTTP 302 during SSR; RESPONSE_INIT is null during CSR/build
|
|
267
|
-
this.responseInit.status = 302;
|
|
268
|
-
this.responseInit.headers = new Headers({ Location: '/auth/login' });
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
##### Handle the callback
|
|
275
|
-
|
|
276
|
-
**Client-managed sessions**:
|
|
277
|
-
|
|
278
|
-
Call this on your redirect URI route after the IDP sends the user back. It parses the query parameters from the callback URL, verifies the state matches what was stored during login (CSRF protection), exchanges the authorization code for tokens using PKCE, validates the ID token, and stores the session in the [configured storage](../sdk-core/README.md#storages).
|
|
279
|
-
|
|
280
|
-
```ts
|
|
281
|
-
// src/app/pages/callback/callback.page.ts
|
|
282
|
-
import { Component, type OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
283
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
284
|
-
import { ActivatedRoute, Router } from '@angular/router';
|
|
285
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
286
|
-
|
|
287
|
-
@Component({
|
|
288
|
-
selector: 'app-callback-page',
|
|
289
|
-
template: `
|
|
290
|
-
<section>
|
|
291
|
-
<h1>Logging in...</h1>
|
|
292
|
-
</section>
|
|
293
|
-
`,
|
|
294
|
-
})
|
|
295
|
-
export class CallbackPage implements OnInit {
|
|
296
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
297
|
-
private readonly router = inject(Router);
|
|
298
|
-
private readonly route = inject(ActivatedRoute);
|
|
299
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
300
|
-
|
|
301
|
-
ngOnInit(): void {
|
|
302
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
303
|
-
void this.handleCallback();
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
private async handleCallback(): Promise<void> {
|
|
308
|
-
const params = this.route.snapshot.queryParams;
|
|
309
|
-
|
|
310
|
-
if (params['error'] || params['error_description']) {
|
|
311
|
-
await this.router.navigate(['/error'], { queryParams: params });
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
await this.authService.handleCallback();
|
|
317
|
-
await this.router.navigateByUrl('/profile');
|
|
318
|
-
} catch (error) {
|
|
319
|
-
await this.router.navigate(['/error'], { queryParams: { message: error instanceof Error ? error.message : 'Unknown error' } });
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
**Server-managed sessions**:
|
|
326
|
-
|
|
327
|
-
Forward the callback query string to `/auth/callback` - the Server SDK completes the code exchange and redirects to `postLoginRedirectUri`:
|
|
328
|
-
|
|
329
|
-
```ts
|
|
330
|
-
// src/app/pages/callback/callback.page.ts
|
|
331
|
-
import { Component, type OnInit, PLATFORM_ID, RESPONSE_INIT, inject } from '@angular/core';
|
|
332
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
333
|
-
import { ActivatedRoute } from '@angular/router';
|
|
334
|
-
|
|
335
|
-
@Component({
|
|
336
|
-
selector: 'app-callback-page',
|
|
337
|
-
template: `
|
|
338
|
-
<section>
|
|
339
|
-
<h1>Logging in...</h1>
|
|
340
|
-
</section>
|
|
341
|
-
`,
|
|
342
|
-
})
|
|
343
|
-
export class CallbackPage implements OnInit {
|
|
344
|
-
private readonly route = inject(ActivatedRoute);
|
|
345
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
346
|
-
private readonly responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
347
|
-
|
|
348
|
-
ngOnInit(): void {
|
|
349
|
-
const query = new URLSearchParams(this.route.snapshot.queryParams as Record<string, string>).toString();
|
|
350
|
-
const url = `/auth/callback${query ? `?${query}` : ''}`;
|
|
351
|
-
|
|
352
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
353
|
-
globalThis.location.href = url;
|
|
354
|
-
} else if (this.responseInit) {
|
|
355
|
-
this.responseInit.status = 302;
|
|
356
|
-
this.responseInit.headers = new Headers({ Location: url });
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
##### Registration
|
|
363
|
-
|
|
364
|
-
**Client-managed sessions**:
|
|
365
|
-
|
|
366
|
-
Call this to start the registration flow. It works the same way as `login()` but opens the registration form instead.
|
|
367
|
-
|
|
368
|
-
```ts
|
|
369
|
-
// src/app/pages/register/register.page.ts
|
|
370
|
-
import { Component, type OnInit, inject } from '@angular/core';
|
|
371
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
372
|
-
|
|
373
|
-
@Component({
|
|
374
|
-
selector: 'app-register-page',
|
|
375
|
-
template: `
|
|
376
|
-
<section>
|
|
377
|
-
<h1>Redirecting to registration...</h1>
|
|
378
|
-
</section>
|
|
379
|
-
`,
|
|
380
|
-
})
|
|
381
|
-
export class RegisterPage implements OnInit {
|
|
382
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
383
|
-
|
|
384
|
-
ngOnInit(): void {
|
|
385
|
-
void this.authService.register({
|
|
386
|
-
loginHint: 'user@example.com',
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
**Server-managed sessions**:
|
|
393
|
-
|
|
394
|
-
Skip the client SDK entirely and redirect straight to `/auth/register` - the Server SDK builds the registration request and redirects to the IDP:
|
|
395
|
-
|
|
396
|
-
```ts
|
|
397
|
-
// src/app/pages/register/register.page.ts
|
|
398
|
-
import { Component, type OnInit, PLATFORM_ID, RESPONSE_INIT, inject } from '@angular/core';
|
|
399
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
400
|
-
|
|
401
|
-
@Component({
|
|
402
|
-
selector: 'app-register-page',
|
|
403
|
-
template: '',
|
|
404
|
-
})
|
|
405
|
-
export class RegisterPage implements OnInit {
|
|
406
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
407
|
-
private readonly responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
408
|
-
|
|
409
|
-
ngOnInit(): void {
|
|
410
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
411
|
-
globalThis.location.href = '/auth/register';
|
|
412
|
-
} else if (this.responseInit) {
|
|
413
|
-
this.responseInit.status = 302;
|
|
414
|
-
this.responseInit.headers = new Headers({ Location: '/auth/register' });
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
##### Logout
|
|
421
|
-
|
|
422
|
-
**Client-managed sessions**:
|
|
423
|
-
|
|
424
|
-
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`.
|
|
425
|
-
|
|
426
|
-
```ts
|
|
427
|
-
// src/app/pages/logout/logout.page.ts
|
|
428
|
-
import { Component, type OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
429
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
430
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
431
|
-
|
|
432
|
-
@Component({
|
|
433
|
-
selector: 'app-logout-page',
|
|
434
|
-
template: `
|
|
435
|
-
<section>
|
|
436
|
-
<h1>Logging out...</h1>
|
|
437
|
-
</section>
|
|
438
|
-
`,
|
|
439
|
-
})
|
|
440
|
-
export class LogoutPage implements OnInit {
|
|
441
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
442
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
443
|
-
|
|
444
|
-
ngOnInit(): void {
|
|
445
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
446
|
-
void this.authService.logout();
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
**Server-managed sessions**:
|
|
453
|
-
|
|
454
|
-
With `serverSessionUri` configured, redirect to `/auth/logout` instead - the Server SDK clears the session and redirects to the IDP end-session endpoint:
|
|
455
|
-
|
|
456
|
-
```ts
|
|
457
|
-
// src/app/pages/logout/logout.page.ts
|
|
458
|
-
import { Component, type OnInit, PLATFORM_ID, RESPONSE_INIT, inject } from '@angular/core';
|
|
459
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
460
|
-
|
|
461
|
-
@Component({
|
|
462
|
-
selector: 'app-logout-page',
|
|
463
|
-
template: `
|
|
464
|
-
<section>
|
|
465
|
-
<h1>Logging out...</h1>
|
|
466
|
-
</section>
|
|
467
|
-
`,
|
|
468
|
-
})
|
|
469
|
-
export class LogoutPage implements OnInit {
|
|
470
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
471
|
-
private readonly responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
472
|
-
|
|
473
|
-
ngOnInit(): void {
|
|
474
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
475
|
-
globalThis.location.href = '/auth/logout';
|
|
476
|
-
} else if (this.responseInit) {
|
|
477
|
-
this.responseInit.status = 302;
|
|
478
|
-
this.responseInit.headers = new Headers({ Location: '/auth/logout' });
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
##### Token management
|
|
485
|
-
|
|
486
|
-
**Client-managed sessions**:
|
|
487
|
-
|
|
488
|
-
Call these methods to manage the session and access token client-side.
|
|
489
|
-
|
|
490
|
-
```ts
|
|
491
|
-
import '@strivacity/common/components/token-field';
|
|
492
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
|
|
493
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
494
|
-
|
|
495
|
-
@Component({
|
|
496
|
-
selector: 'app-token-panel',
|
|
497
|
-
template: `
|
|
498
|
-
@if (!authService.loading()) {
|
|
499
|
-
<div>
|
|
500
|
-
<button (click)="onRefresh()">Refresh</button>
|
|
501
|
-
<button (click)="onRevoke()">Revoke</button>
|
|
502
|
-
<pre>{{ { idTokenClaims: authService.idTokenClaims(), accessToken: authService.accessToken(), refreshToken: authService.refreshToken() } | json }}</pre>
|
|
503
|
-
</div>
|
|
504
|
-
}
|
|
505
|
-
`,
|
|
506
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
507
|
-
})
|
|
508
|
-
export class TokenPanelComponent {
|
|
509
|
-
readonly authService = inject(StrivacityAuthService);
|
|
510
|
-
|
|
511
|
-
async onRefresh(): Promise<void> {
|
|
512
|
-
// Refresh the access token using the refresh token
|
|
513
|
-
await this.authService.refresh();
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
async onRevoke(): Promise<void> {
|
|
517
|
-
// Revoke all tokens at the authorization server and clear the local session
|
|
518
|
-
await this.authService.revoke();
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
**Server-managed sessions**:
|
|
524
|
-
|
|
525
|
-
With `serverSessionUri` configured, tokens are refreshed/revoked by the Server SDK - trigger it by navigating to the auth routes, then let the router redirect back:
|
|
526
|
-
|
|
527
|
-
```ts
|
|
528
|
-
function onRefresh() {
|
|
529
|
-
// sdk.refreshSession() runs server-side, then redirects back to returnTo
|
|
530
|
-
globalThis.location.href = '/auth/refresh?returnTo=/profile';
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function onRevoke() {
|
|
534
|
-
// sdk.revokeSession() runs server-side, then redirects to postLogoutRedirectUri
|
|
535
|
-
globalThis.location.href = '/auth/revoke';
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
---
|
|
540
|
-
|
|
541
|
-
#### popup mode
|
|
542
|
-
|
|
543
|
-
> For details on how this mode works, see the [hosted journey documentation](https://docs.strivacity.com/reference/hosted-journey).
|
|
544
|
-
|
|
545
|
-
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.
|
|
546
|
-
|
|
547
|
-
##### Login
|
|
548
|
-
|
|
549
|
-
**Client-managed sessions**:
|
|
550
|
-
|
|
551
|
-
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).
|
|
552
|
-
|
|
553
|
-
By default a centered popup window opens. Pass `popupWindowTarget` to change where the window opens, and `popupWindowFeatures` to control its size and position:
|
|
554
|
-
|
|
555
|
-
```ts
|
|
556
|
-
// src/app/pages/login/login.page.ts
|
|
557
|
-
import { Component, type OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
558
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
559
|
-
import { Router } from '@angular/router';
|
|
560
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
561
|
-
import type { PopupFlow } from '@strivacity/sdk-angular';
|
|
562
|
-
|
|
563
|
-
@Component({
|
|
564
|
-
selector: 'app-login-page',
|
|
565
|
-
template: `
|
|
566
|
-
<section>
|
|
567
|
-
<h1>Opening login popup...</h1>
|
|
568
|
-
</section>
|
|
569
|
-
`,
|
|
570
|
-
})
|
|
571
|
-
export class LoginPage implements OnInit {
|
|
572
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
573
|
-
private readonly router = inject(Router);
|
|
574
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
575
|
-
|
|
576
|
-
ngOnInit(): void {
|
|
577
|
-
if (!isPlatformBrowser(this.platformId)) {
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
void this.startLogin();
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
private async startLogin(): Promise<void> {
|
|
585
|
-
try {
|
|
586
|
-
await (this.authService.sdk as PopupFlow).login({
|
|
587
|
-
// Optional parameters
|
|
588
|
-
loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
|
|
589
|
-
acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
|
|
590
|
-
audiences: ['https://api.example.com'], // target resources for the access token
|
|
591
|
-
popupWindowTarget: '_blank', // any valid browsing context name
|
|
592
|
-
popupWindowFeatures: {
|
|
593
|
-
width: 500,
|
|
594
|
-
height: 700,
|
|
595
|
-
left: 100,
|
|
596
|
-
top: 100,
|
|
597
|
-
toolbar: false,
|
|
598
|
-
location: false,
|
|
599
|
-
resizable: true,
|
|
600
|
-
scrollbars: true,
|
|
601
|
-
},
|
|
602
|
-
});
|
|
603
|
-
await this.router.navigateByUrl('/profile');
|
|
604
|
-
} catch (error) {
|
|
605
|
-
await this.router.navigate(['/error'], { queryParams: { message: error instanceof Error ? error.message : 'Unknown error' } });
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
**Server-managed sessions**:
|
|
612
|
-
|
|
613
|
-
Popup mode always needs client-side JavaScript to open the window, so there's no server-only alternative here.
|
|
614
|
-
|
|
615
|
-
##### Handle the callback
|
|
616
|
-
|
|
617
|
-
**Client-managed sessions**:
|
|
618
|
-
|
|
619
|
-
The popup resolves automatically - no callback route is needed. Token exchange happens inside the popup and the result is posted back to the opener window.
|
|
620
|
-
|
|
621
|
-
**Server-managed sessions**:
|
|
622
|
-
|
|
623
|
-
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.
|
|
624
|
-
|
|
625
|
-
##### Registration
|
|
626
|
-
|
|
627
|
-
**Client-managed sessions**:
|
|
628
|
-
|
|
629
|
-
Call this to start the registration flow. It works the same way as `login()` but opens the registration form instead.
|
|
630
|
-
|
|
631
|
-
```ts
|
|
632
|
-
// src/app/pages/register/register.page.ts
|
|
633
|
-
import { Component, type OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
634
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
635
|
-
import { Router } from '@angular/router';
|
|
636
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
637
|
-
import type { PopupFlow } from '@strivacity/sdk-angular';
|
|
638
|
-
|
|
639
|
-
@Component({
|
|
640
|
-
selector: 'app-register-page',
|
|
641
|
-
template: `
|
|
642
|
-
<section>
|
|
643
|
-
<h1>Opening registration popup...</h1>
|
|
644
|
-
</section>
|
|
645
|
-
`,
|
|
646
|
-
})
|
|
647
|
-
export class RegisterPage implements OnInit {
|
|
648
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
649
|
-
private readonly router = inject(Router);
|
|
650
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
651
|
-
|
|
652
|
-
ngOnInit(): void {
|
|
653
|
-
if (!isPlatformBrowser(this.platformId)) {
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
void this.startRegister();
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
private async startRegister(): Promise<void> {
|
|
661
|
-
try {
|
|
662
|
-
await (this.authService.sdk as PopupFlow).register({
|
|
663
|
-
// Optional parameters
|
|
664
|
-
loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
|
|
665
|
-
acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
|
|
666
|
-
audiences: ['https://api.example.com'], // target resources for the access token
|
|
667
|
-
popupWindowTarget: '_blank', // any valid browsing context name
|
|
668
|
-
popupWindowFeatures: {
|
|
669
|
-
width: 500,
|
|
670
|
-
height: 700,
|
|
671
|
-
left: 100,
|
|
672
|
-
top: 100,
|
|
673
|
-
toolbar: false,
|
|
674
|
-
location: false,
|
|
675
|
-
resizable: true,
|
|
676
|
-
scrollbars: true,
|
|
677
|
-
},
|
|
678
|
-
});
|
|
679
|
-
await this.router.navigateByUrl('/profile');
|
|
680
|
-
} catch (error) {
|
|
681
|
-
await this.router.navigate(['/error'], { queryParams: { message: error instanceof Error ? error.message : 'Unknown error' } });
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
**Server-managed sessions**:
|
|
688
|
-
|
|
689
|
-
Popup mode always needs client-side JavaScript to open the window, so there's no server-only alternative here.
|
|
690
|
-
|
|
691
|
-
##### Logout
|
|
692
|
-
|
|
693
|
-
**Client-managed sessions**:
|
|
694
|
-
|
|
695
|
-
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`.
|
|
696
|
-
|
|
697
|
-
```ts
|
|
698
|
-
// src/app/pages/logout/logout.page.ts
|
|
699
|
-
import { Component, type OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
700
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
701
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
702
|
-
|
|
703
|
-
@Component({
|
|
704
|
-
selector: 'app-logout-page',
|
|
705
|
-
template: `
|
|
706
|
-
<section>
|
|
707
|
-
<h1>Logging out...</h1>
|
|
708
|
-
</section>
|
|
709
|
-
`,
|
|
710
|
-
})
|
|
711
|
-
export class LogoutPage implements OnInit {
|
|
712
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
713
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
714
|
-
|
|
715
|
-
ngOnInit(): void {
|
|
716
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
717
|
-
void this.authService.logout();
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
**Server-managed sessions**:
|
|
724
|
-
|
|
725
|
-
With `serverSessionUri` configured, redirect to `/auth/logout` instead - the Server SDK clears the session and redirects to the IDP end-session endpoint:
|
|
726
|
-
|
|
727
|
-
```ts
|
|
728
|
-
// src/app/pages/logout/logout.page.ts
|
|
729
|
-
import { Component, type OnInit, PLATFORM_ID, RESPONSE_INIT, inject } from '@angular/core';
|
|
730
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
731
|
-
|
|
732
|
-
@Component({
|
|
733
|
-
selector: 'app-logout-page',
|
|
734
|
-
template: `
|
|
735
|
-
<section>
|
|
736
|
-
<h1>Logging out...</h1>
|
|
737
|
-
</section>
|
|
738
|
-
`,
|
|
739
|
-
})
|
|
740
|
-
export class LogoutPage implements OnInit {
|
|
741
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
742
|
-
private readonly responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
743
|
-
|
|
744
|
-
ngOnInit(): void {
|
|
745
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
746
|
-
globalThis.location.href = '/auth/logout';
|
|
747
|
-
} else if (this.responseInit) {
|
|
748
|
-
this.responseInit.status = 302;
|
|
749
|
-
this.responseInit.headers = new Headers({ Location: '/auth/logout' });
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
##### Token management
|
|
756
|
-
|
|
757
|
-
**Client-managed sessions**:
|
|
758
|
-
|
|
759
|
-
Call these methods to manage the session and access token client-side.
|
|
760
|
-
|
|
761
|
-
```ts
|
|
762
|
-
import '@strivacity/common/components/token-field';
|
|
763
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
|
|
764
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
765
|
-
|
|
766
|
-
@Component({
|
|
767
|
-
selector: 'app-token-panel',
|
|
768
|
-
template: `
|
|
769
|
-
@if (!authService.loading()) {
|
|
770
|
-
<div>
|
|
771
|
-
<button (click)="onRefresh()">Refresh</button>
|
|
772
|
-
<button (click)="onRevoke()">Revoke</button>
|
|
773
|
-
<pre>{{ { idTokenClaims: authService.idTokenClaims(), accessToken: authService.accessToken(), refreshToken: authService.refreshToken() } | json }}</pre>
|
|
774
|
-
</div>
|
|
775
|
-
}
|
|
776
|
-
`,
|
|
777
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
778
|
-
})
|
|
779
|
-
export class TokenPanelComponent {
|
|
780
|
-
readonly authService = inject(StrivacityAuthService);
|
|
781
|
-
|
|
782
|
-
async onRefresh(): Promise<void> {
|
|
783
|
-
// Refresh the access token using the refresh token
|
|
784
|
-
await this.authService.refresh();
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
async onRevoke(): Promise<void> {
|
|
788
|
-
// Revoke all tokens at the authorization server and clear the local session
|
|
789
|
-
await this.authService.revoke();
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
**Server-managed sessions**:
|
|
795
|
-
|
|
796
|
-
With `serverSessionUri` configured, tokens are refreshed/revoked by the Server SDK - trigger it by navigating to the auth routes, then let the router redirect back:
|
|
797
|
-
|
|
798
|
-
```ts
|
|
799
|
-
function onRefresh() {
|
|
800
|
-
// sdk.refreshSession() runs server-side, then redirects back to returnTo
|
|
801
|
-
globalThis.location.href = '/auth/refresh?returnTo=/profile';
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function onRevoke() {
|
|
805
|
-
// sdk.revokeSession() runs server-side, then redirects to postLogoutRedirectUri
|
|
806
|
-
globalThis.location.href = '/auth/revoke';
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
---
|
|
811
|
-
|
|
812
|
-
#### embedded mode
|
|
813
|
-
|
|
814
|
-
> For details on how this mode works, see the [embedded journey documentation](https://docs.strivacity.com/reference/embedded-journey).
|
|
815
|
-
|
|
816
|
-
The login UI renders inside your own page using Strivacity web components (`<sty-login>`, `<sty-notifications>`, `<sty-language-selector>`). The component bundle isn't an npm package - load it dynamically from your Strivacity tenant cluster once, on the login route. Angular binds non-string inputs (like `params`) as properties via `[...]` and wires up custom events via `(...)`, so there's no manual ref/listener wiring needed for the basic case - just remember to add `schemas: [CUSTOM_ELEMENTS_SCHEMA]` to the component:
|
|
817
|
-
|
|
818
|
-
##### Login / Register
|
|
819
|
-
|
|
820
|
-
**Client-managed sessions**:
|
|
821
|
-
|
|
822
|
-
```ts
|
|
823
|
-
// src/app/pages/login/login.page.ts
|
|
824
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, type OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
825
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
826
|
-
import { ActivatedRoute, Router } from '@angular/router';
|
|
827
|
-
import { injectScript, StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
828
|
-
|
|
829
|
-
@Component({
|
|
830
|
-
selector: 'app-login-page',
|
|
831
|
-
template: `
|
|
832
|
-
<section>
|
|
833
|
-
<sty-notifications></sty-notifications>
|
|
834
|
-
<sty-login [params]="params" [sessionId]="sessionId" [shortAppId]="shortAppId" [lang]="language" (login)="onLogin()" (close)="onClose()" (error)="onError($event)"></sty-login>
|
|
835
|
-
<sty-language-selector></sty-language-selector>
|
|
836
|
-
</section>
|
|
837
|
-
`,
|
|
838
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
839
|
-
})
|
|
840
|
-
export class LoginPage implements OnInit {
|
|
841
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
842
|
-
private readonly router = inject(Router);
|
|
843
|
-
private readonly route = inject(ActivatedRoute);
|
|
844
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
845
|
-
|
|
846
|
-
readonly params = {
|
|
847
|
-
loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
|
|
848
|
-
acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
|
|
849
|
-
audiences: ['https://api.example.com'], // target resources for the access token
|
|
850
|
-
language: 'en-US', // set the UI language (BCP 47 language tag)
|
|
851
|
-
prompt: 'login', // use 'create' to open the registration flow instead
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
// Optional: Resume a session started from an entry URL (e.g., password reset)
|
|
855
|
-
readonly sessionId = this.route.snapshot.queryParamMap.get('session_id');
|
|
856
|
-
readonly shortAppId = this.route.snapshot.queryParamMap.get('short_app_id');
|
|
857
|
-
readonly language = this.route.snapshot.queryParamMap.get('language') ?? (isPlatformBrowser(this.platformId) ? globalThis.navigator.language : 'en-US');
|
|
858
|
-
|
|
859
|
-
ngOnInit(): void {
|
|
860
|
-
if (!isPlatformBrowser(this.platformId)) {
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// injectScript loads the <sty-login>/<sty-notifications>/<sty-language-selector>
|
|
865
|
-
// custom element definitions from the auth server
|
|
866
|
-
injectScript('sty-components', `${this.authService.sdk.options.issuer}/assets/components/bundle.js`);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
onLogin(): void {
|
|
870
|
-
void this.router.navigateByUrl('/profile');
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
onClose(): void {
|
|
874
|
-
globalThis.location.reload();
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
onError(event: Event): void {
|
|
878
|
-
void this.router.navigate(['/error'], { queryParams: { message: (event as CustomEvent<string>).detail } });
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
```
|
|
882
|
-
|
|
883
|
-
**Server-managed sessions**:
|
|
884
|
-
|
|
885
|
-
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.
|
|
886
|
-
|
|
887
|
-
##### Controlling when the flow starts
|
|
888
|
-
|
|
889
|
-
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:
|
|
890
|
-
|
|
891
|
-
```ts
|
|
892
|
-
// src/app/pages/login/login.page.ts
|
|
893
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, type AfterViewInit, ElementRef, inject, viewChild } from '@angular/core';
|
|
894
|
-
import { Router } from '@angular/router';
|
|
895
|
-
import type { LoginComponent } from '@strivacity/sdk-angular/types';
|
|
896
|
-
|
|
897
|
-
@Component({
|
|
898
|
-
selector: 'app-login-page',
|
|
899
|
-
template: `
|
|
900
|
-
<section>
|
|
901
|
-
<sty-notifications></sty-notifications>
|
|
902
|
-
<button (click)="onStartClick()">Continue to login</button>
|
|
903
|
-
<sty-login #loginEl lazy (login)="onLogin()"></sty-login>
|
|
904
|
-
<sty-language-selector></sty-language-selector>
|
|
905
|
-
</section>
|
|
906
|
-
`,
|
|
907
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
908
|
-
})
|
|
909
|
-
export class LoginPage {
|
|
910
|
-
private readonly router = inject(Router);
|
|
911
|
-
|
|
912
|
-
readonly loginEl = viewChild.required<ElementRef<LoginComponent>>('loginEl');
|
|
913
|
-
|
|
914
|
-
async onStartClick(): Promise<void> {
|
|
915
|
-
await this.loginEl().nativeElement.start({
|
|
916
|
-
loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
|
|
917
|
-
acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
|
|
918
|
-
audiences: ['https://api.example.com'], // target resources for the access token
|
|
919
|
-
language: 'en-US', // set the UI language (BCP 47 language tag)
|
|
920
|
-
prompt: 'login', // use 'create' to open the registration flow instead
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
onLogin(): void {
|
|
925
|
-
void this.router.navigateByUrl('/profile');
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
```
|
|
929
|
-
|
|
930
|
-
You can also set params via the `params` property before calling `start()`:
|
|
931
|
-
|
|
932
|
-
```ts
|
|
933
|
-
// src/app/pages/login/login.page.ts
|
|
934
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, viewChild } from '@angular/core';
|
|
935
|
-
import type { LoginComponent } from '@strivacity/sdk-angular/types';
|
|
936
|
-
|
|
937
|
-
@Component({
|
|
938
|
-
selector: 'app-login-page',
|
|
939
|
-
template: `
|
|
940
|
-
<sty-login #loginEl lazy></sty-login>
|
|
941
|
-
<button (click)="onStartClick()">Start Login</button>
|
|
942
|
-
`,
|
|
943
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
944
|
-
})
|
|
945
|
-
export class LoginPage {
|
|
946
|
-
readonly loginEl = viewChild.required<ElementRef<LoginComponent>>('loginEl');
|
|
947
|
-
|
|
948
|
-
async onStartClick(): Promise<void> {
|
|
949
|
-
const element = this.loginEl().nativeElement;
|
|
950
|
-
|
|
951
|
-
element.params = {
|
|
952
|
-
loginHint: 'user@example.com', // identifier or JWT-encoded data to hint the login flow
|
|
953
|
-
acrValues: ['urn:strivacity:loa:2'], // request specific authentication context
|
|
954
|
-
audiences: ['https://api.example.com'], // target resources for the access token
|
|
955
|
-
language: 'en-US', // set the UI language (BCP 47 language tag)
|
|
956
|
-
prompt: 'login', // use 'create' to open the registration flow instead
|
|
957
|
-
};
|
|
958
|
-
await element.start();
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
##### Login events
|
|
964
|
-
|
|
965
|
-
The `<sty-login>` element dispatches `login`, `close`, and `error` custom events - bind them the same way as `(login)`/`(close)`/`(error)` shown above. If you need to attach/detach listeners manually instead (e.g. conditionally), grab the element via `viewChild` and use `addEventListener` in `ngAfterViewInit`/`ngOnDestroy`:
|
|
966
|
-
|
|
967
|
-
```ts
|
|
968
|
-
// src/app/pages/login/login.page.ts
|
|
969
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, type AfterViewInit, type OnDestroy, ElementRef, inject, viewChild } from '@angular/core';
|
|
970
|
-
import { Router } from '@angular/router';
|
|
971
|
-
import type { LoginComponent } from '@strivacity/sdk-angular/types';
|
|
972
|
-
|
|
973
|
-
@Component({
|
|
974
|
-
selector: 'app-login-page',
|
|
975
|
-
template: `
|
|
976
|
-
<section>
|
|
977
|
-
<sty-notifications></sty-notifications>
|
|
978
|
-
<sty-login #loginEl></sty-login>
|
|
979
|
-
<sty-language-selector></sty-language-selector>
|
|
980
|
-
</section>
|
|
981
|
-
`,
|
|
982
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
983
|
-
})
|
|
984
|
-
export class LoginPage implements AfterViewInit, OnDestroy {
|
|
985
|
-
private readonly router = inject(Router);
|
|
986
|
-
|
|
987
|
-
readonly loginEl = viewChild.required<ElementRef<LoginComponent>>('loginEl');
|
|
988
|
-
|
|
989
|
-
private onLogin = () => {
|
|
990
|
-
// User authenticated - navigate to a protected page
|
|
991
|
-
void this.router.navigateByUrl('/profile');
|
|
992
|
-
};
|
|
993
|
-
private onClose = () => {
|
|
994
|
-
// User cancelled or closed the login flow
|
|
995
|
-
globalThis.location.reload();
|
|
996
|
-
};
|
|
997
|
-
private onError = (event: Event) => {
|
|
998
|
-
// A fatal error occurred - the message is available in event.detail
|
|
999
|
-
void this.router.navigate(['/error'], { queryParams: { message: (event as CustomEvent<string>).detail } });
|
|
1000
|
-
};
|
|
1001
|
-
|
|
1002
|
-
ngAfterViewInit(): void {
|
|
1003
|
-
const element = this.loginEl().nativeElement;
|
|
1004
|
-
|
|
1005
|
-
element.addEventListener('login', this.onLogin);
|
|
1006
|
-
element.addEventListener('close', this.onClose);
|
|
1007
|
-
element.addEventListener('error', this.onError);
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
ngOnDestroy(): void {
|
|
1011
|
-
const element = this.loginEl().nativeElement;
|
|
1012
|
-
|
|
1013
|
-
element.removeEventListener('login', this.onLogin);
|
|
1014
|
-
element.removeEventListener('close', this.onClose);
|
|
1015
|
-
element.removeEventListener('error', this.onError);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
```
|
|
1019
|
-
|
|
1020
|
-
##### Notification events
|
|
1021
|
-
|
|
1022
|
-
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:
|
|
1023
|
-
|
|
1024
|
-
```ts
|
|
1025
|
-
import { Component, type OnDestroy, type OnInit } from '@angular/core';
|
|
1026
|
-
|
|
1027
|
-
@Component({ selector: 'app-custom-notifications', template: '' })
|
|
1028
|
-
export class CustomNotificationsComponent implements OnInit, OnDestroy {
|
|
1029
|
-
private onNotification(event: Event): void {
|
|
1030
|
-
const customEvent = event as CustomEvent;
|
|
1031
|
-
|
|
1032
|
-
if (customEvent.detail.action === 'show') {
|
|
1033
|
-
// Add new notification to your custom notification system
|
|
1034
|
-
console.log('New notification:', customEvent.detail.notification);
|
|
1035
|
-
} else if (customEvent.detail.action === 'clear') {
|
|
1036
|
-
console.log('Clear all notifications');
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
ngOnInit(): void {
|
|
1041
|
-
document.addEventListener('notification', this.onNotification);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
ngOnDestroy(): void {
|
|
1045
|
-
document.removeEventListener('notification', this.onNotification);
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
```
|
|
1049
|
-
|
|
1050
|
-
##### Dynamic language switching
|
|
1051
|
-
|
|
1052
|
-
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:
|
|
1053
|
-
|
|
1054
|
-
```ts
|
|
1055
|
-
import { Component } from '@angular/core';
|
|
1056
|
-
|
|
1057
|
-
@Component({
|
|
1058
|
-
selector: 'app-language-switcher',
|
|
1059
|
-
template: `
|
|
1060
|
-
<section>
|
|
1061
|
-
<div>
|
|
1062
|
-
<button (click)="currentLang = 'en-US'">English</button>
|
|
1063
|
-
<button (click)="currentLang = 'fr-FR'">Français</button>
|
|
1064
|
-
<button (click)="currentLang = 'de-DE'">Deutsch</button>
|
|
1065
|
-
</div>
|
|
1066
|
-
<sty-login [lang]="currentLang"></sty-login>
|
|
1067
|
-
</section>
|
|
1068
|
-
`,
|
|
1069
|
-
})
|
|
1070
|
-
export class LanguageSwitcherComponent {
|
|
1071
|
-
currentLang = 'en-US';
|
|
1072
|
-
}
|
|
1073
|
-
```
|
|
1074
|
-
|
|
1075
|
-
##### Handle the callback
|
|
1076
|
-
|
|
1077
|
-
**Client-managed sessions**:
|
|
1078
|
-
|
|
1079
|
-
No separate callback route is needed. The `<sty-login>` component handles the entire authentication flow automatically, including token exchange, and dispatches a `login` event when authentication completes successfully.
|
|
1080
|
-
|
|
1081
|
-
**Server-managed sessions**:
|
|
1082
|
-
|
|
1083
|
-
Same as client managed - the component's internal callback request is also transparently proxied through `/auth/callback`, with no separate route needed either way.
|
|
1084
|
-
|
|
1085
|
-
#### Externally-initiated flows (entry)
|
|
1086
|
-
|
|
1087
|
-
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`).
|
|
1088
|
-
|
|
1089
|
-
You have two options:
|
|
1090
|
-
|
|
1091
|
-
**Option 1: Redirect to a separate login route**
|
|
1092
|
-
|
|
1093
|
-
Forward the parameters as query params to your login route:
|
|
1094
|
-
|
|
1095
|
-
```ts
|
|
1096
|
-
// src/app/pages/entry/entry.page.ts
|
|
1097
|
-
import { Component, type OnInit, inject } from '@angular/core';
|
|
1098
|
-
import { Router } from '@angular/router';
|
|
1099
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
1100
|
-
import type { EmbeddedFlow } from '@strivacity/sdk-angular/types';
|
|
1101
|
-
@Component({
|
|
1102
|
-
selector: 'app-entry-page',
|
|
1103
|
-
template: `
|
|
1104
|
-
<section>
|
|
1105
|
-
<h1>Loading...</h1>
|
|
1106
|
-
</section>
|
|
1107
|
-
`,
|
|
1108
|
-
})
|
|
1109
|
-
export class EntryPage implements OnInit {
|
|
1110
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
1111
|
-
private readonly router = inject(Router);
|
|
1112
|
-
|
|
1113
|
-
ngOnInit(): void {
|
|
1114
|
-
void this.handleEntry();
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
private async handleEntry(): Promise<void> {
|
|
1118
|
-
try {
|
|
1119
|
-
const data = await (this.authService.sdk as EmbeddedFlow).entry();
|
|
1120
|
-
// Redirect to login route with flow parameters
|
|
1121
|
-
const params = new URLSearchParams({
|
|
1122
|
-
session_id: data.session_id,
|
|
1123
|
-
short_app_id: data.short_app_id,
|
|
1124
|
-
language: data.language,
|
|
1125
|
-
});
|
|
1126
|
-
globalThis.location.href = `/login?${params}`;
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
await this.router.navigate(['/error'], { queryParams: { message: error instanceof Error ? error.message : 'Unknown error' } });
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
```
|
|
1133
|
-
|
|
1134
|
-
Then on your login route, read the parameters and pass them to `<sty-login>`:
|
|
1135
|
-
|
|
1136
|
-
```ts
|
|
1137
|
-
// src/app/pages/login/login.page.ts
|
|
1138
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
|
|
1139
|
-
import { ActivatedRoute, Router } from '@angular/router';
|
|
1140
|
-
|
|
1141
|
-
@Component({
|
|
1142
|
-
selector: 'app-login-page',
|
|
1143
|
-
template: `
|
|
1144
|
-
<section>
|
|
1145
|
-
<sty-notifications></sty-notifications>
|
|
1146
|
-
<sty-login [sessionId]="sessionId" [shortAppId]="shortAppId" [lang]="language" (login)="onLogin()" (error)="onError($event)"></sty-login>
|
|
1147
|
-
<sty-language-selector></sty-language-selector>
|
|
1148
|
-
</section>
|
|
1149
|
-
`,
|
|
1150
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
1151
|
-
})
|
|
1152
|
-
export class LoginPage {
|
|
1153
|
-
private readonly router = inject(Router);
|
|
1154
|
-
private readonly route = inject(ActivatedRoute);
|
|
1155
|
-
|
|
1156
|
-
// Read parameters from URL
|
|
1157
|
-
readonly sessionId = this.route.snapshot.queryParamMap.get('session_id');
|
|
1158
|
-
readonly shortAppId = this.route.snapshot.queryParamMap.get('short_app_id');
|
|
1159
|
-
readonly language = this.route.snapshot.queryParamMap.get('language');
|
|
1160
|
-
|
|
1161
|
-
onLogin(): void {
|
|
1162
|
-
void this.router.navigateByUrl('/profile');
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
onError(event: Event): void {
|
|
1166
|
-
void this.router.navigate(['/error'], { queryParams: { message: (event as CustomEvent<string>).detail } });
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
```
|
|
1170
|
-
|
|
1171
|
-
**Option 2: Render login on the entry route**
|
|
1172
|
-
|
|
1173
|
-
Pass the parameters directly to `<sty-login>` on the same route:
|
|
1174
|
-
|
|
1175
|
-
```ts
|
|
1176
|
-
// src/app/pages/entry/entry.page.ts
|
|
1177
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, type OnInit, inject, signal } from '@angular/core';
|
|
1178
|
-
import { Router } from '@angular/router';
|
|
1179
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
1180
|
-
import type { EmbeddedFlow } from '@strivacity/sdk-angular/types';
|
|
1181
|
-
|
|
1182
|
-
@Component({
|
|
1183
|
-
selector: 'app-entry-page',
|
|
1184
|
-
template: `
|
|
1185
|
-
@if (sessionId(); as sessionId) {
|
|
1186
|
-
<section>
|
|
1187
|
-
<sty-notifications></sty-notifications>
|
|
1188
|
-
<sty-login [sessionId]="sessionId" [shortAppId]="shortAppId()" [lang]="language()" (login)="onLogin()" (error)="onError($event)"></sty-login>
|
|
1189
|
-
<sty-language-selector></sty-language-selector>
|
|
1190
|
-
</section>
|
|
1191
|
-
} @else {
|
|
1192
|
-
<section>
|
|
1193
|
-
<h1>Loading...</h1>
|
|
1194
|
-
</section>
|
|
1195
|
-
}
|
|
1196
|
-
`,
|
|
1197
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
1198
|
-
})
|
|
1199
|
-
export class EntryPage implements OnInit {
|
|
1200
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
1201
|
-
private readonly router = inject(Router);
|
|
1202
|
-
|
|
1203
|
-
readonly sessionId = signal<string | null>(null);
|
|
1204
|
-
readonly shortAppId = signal<string | null>(null);
|
|
1205
|
-
readonly language = signal<string | null>(null);
|
|
1206
|
-
|
|
1207
|
-
ngOnInit(): void {
|
|
1208
|
-
void this.handleEntry();
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
private async handleEntry(): Promise<void> {
|
|
1212
|
-
try {
|
|
1213
|
-
const data = await (this.authService.sdk as EmbeddedFlow).entry();
|
|
1214
|
-
// Set signals for sty-login component
|
|
1215
|
-
this.sessionId.set(data.session_id);
|
|
1216
|
-
this.shortAppId.set(data.short_app_id);
|
|
1217
|
-
this.language.set(data.language);
|
|
1218
|
-
} catch (error) {
|
|
1219
|
-
await this.router.navigate(['/error'], { queryParams: { message: error instanceof Error ? error.message : 'Unknown error' } });
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
onLogin(): void {
|
|
1224
|
-
void this.router.navigateByUrl('/profile');
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
onError(event: Event): void {
|
|
1228
|
-
void this.router.navigate(['/error'], { queryParams: { message: (event as CustomEvent<string>).detail } });
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
```
|
|
1232
|
-
|
|
1233
|
-
##### Logout
|
|
1234
|
-
|
|
1235
|
-
**Client-managed sessions**:
|
|
1236
|
-
|
|
1237
|
-
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`.
|
|
1238
|
-
|
|
1239
|
-
```ts
|
|
1240
|
-
// src/app/pages/logout/logout.page.ts
|
|
1241
|
-
import { Component, type OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
1242
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
1243
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
1244
|
-
|
|
1245
|
-
@Component({
|
|
1246
|
-
selector: 'app-logout-page',
|
|
1247
|
-
template: `
|
|
1248
|
-
<section>
|
|
1249
|
-
<h1>Logging out...</h1>
|
|
1250
|
-
</section>
|
|
1251
|
-
`,
|
|
1252
|
-
})
|
|
1253
|
-
export class LogoutPage implements OnInit {
|
|
1254
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
1255
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
1256
|
-
|
|
1257
|
-
ngOnInit(): void {
|
|
1258
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
1259
|
-
void this.authService.logout();
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
```
|
|
1264
|
-
|
|
1265
|
-
**Server-managed sessions**:
|
|
1266
|
-
|
|
1267
|
-
With `serverSessionUri` configured, redirect to `/auth/logout` instead - the Server SDK clears the session and redirects to the IDP end-session endpoint:
|
|
1268
|
-
|
|
1269
|
-
```ts
|
|
1270
|
-
// src/app/pages/logout/logout.page.ts
|
|
1271
|
-
import { Component, type OnInit, PLATFORM_ID, RESPONSE_INIT, inject } from '@angular/core';
|
|
1272
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
1273
|
-
|
|
1274
|
-
@Component({
|
|
1275
|
-
selector: 'app-logout-page',
|
|
1276
|
-
template: `
|
|
1277
|
-
<section>
|
|
1278
|
-
<h1>Logging out...</h1>
|
|
1279
|
-
</section>
|
|
1280
|
-
`,
|
|
1281
|
-
})
|
|
1282
|
-
export class LogoutPage implements OnInit {
|
|
1283
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
1284
|
-
private readonly responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
1285
|
-
|
|
1286
|
-
ngOnInit(): void {
|
|
1287
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
1288
|
-
globalThis.location.href = '/auth/logout';
|
|
1289
|
-
} else if (this.responseInit) {
|
|
1290
|
-
this.responseInit.status = 302;
|
|
1291
|
-
this.responseInit.headers = new Headers({ Location: '/auth/logout' });
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
```
|
|
1296
|
-
|
|
1297
|
-
##### Token management
|
|
1298
|
-
|
|
1299
|
-
**Client-managed sessions**:
|
|
1300
|
-
|
|
1301
|
-
Call these methods to manage the session and access token client-side.
|
|
1302
|
-
|
|
1303
|
-
```ts
|
|
1304
|
-
import '@strivacity/common/components/token-field';
|
|
1305
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
|
|
1306
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
1307
|
-
|
|
1308
|
-
@Component({
|
|
1309
|
-
selector: 'app-token-panel',
|
|
1310
|
-
template: `
|
|
1311
|
-
@if (!authService.loading()) {
|
|
1312
|
-
<div>
|
|
1313
|
-
<button (click)="onRefresh()">Refresh</button>
|
|
1314
|
-
<button (click)="onRevoke()">Revoke</button>
|
|
1315
|
-
<pre>{{ { idTokenClaims: authService.idTokenClaims(), accessToken: authService.accessToken(), refreshToken: authService.refreshToken() } | json }}</pre>
|
|
1316
|
-
</div>
|
|
1317
|
-
}
|
|
1318
|
-
`,
|
|
1319
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
1320
|
-
})
|
|
1321
|
-
export class TokenPanelComponent {
|
|
1322
|
-
readonly authService = inject(StrivacityAuthService);
|
|
1323
|
-
|
|
1324
|
-
async onRefresh(): Promise<void> {
|
|
1325
|
-
// Refresh the access token using the refresh token
|
|
1326
|
-
await this.authService.refresh();
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
async onRevoke(): Promise<void> {
|
|
1330
|
-
// Revoke all tokens at the authorization server and clear the local session
|
|
1331
|
-
await this.authService.revoke();
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
```
|
|
1335
|
-
|
|
1336
|
-
**Server-managed sessions**:
|
|
1337
|
-
|
|
1338
|
-
With `serverSessionUri` configured, tokens are refreshed/revoked by the Server SDK - trigger it by navigating to the auth routes, then let the router redirect back:
|
|
1339
|
-
|
|
1340
|
-
```ts
|
|
1341
|
-
function onRefresh() {
|
|
1342
|
-
// sdk.refreshSession() runs server-side, then redirects back to returnTo
|
|
1343
|
-
globalThis.location.href = '/auth/refresh?returnTo=/profile';
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
function onRevoke() {
|
|
1347
|
-
// sdk.revokeSession() runs server-side, then redirects to postLogoutRedirectUri
|
|
1348
|
-
globalThis.location.href = '/auth/revoke';
|
|
1349
|
-
}
|
|
1350
|
-
```
|
|
1351
|
-
|
|
1352
|
-
---
|
|
1353
|
-
|
|
1354
|
-
#### native mode
|
|
1355
|
-
|
|
1356
|
-
> For details on how this mode works, see the [native journey documentation](https://docs.strivacity.com/reference/native-journey).
|
|
1357
|
-
|
|
1358
|
-
You build the entire login UI with your own components. `StrivacityNativeLoginService` 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.
|
|
1359
|
-
|
|
1360
|
-
> `StrivacityNativeLoginService` must be provided per-component (`providers: [StrivacityNativeLoginService]`), and `start(options)` must be called explicitly (e.g. from `ngOnInit`) - it doesn't start automatically like `StrivacityAuthService` does.
|
|
1361
|
-
>
|
|
1362
|
-
> The example below shows a simplified custom implementation. For a complete native renderer with all widget types, see the [example app](../../apps/angular/src/app/components/auth/native/native-login.ts).
|
|
1363
|
-
|
|
1364
|
-
##### Login / Register
|
|
1365
|
-
|
|
1366
|
-
**Client-managed sessions**:
|
|
1367
|
-
|
|
1368
|
-
```ts
|
|
1369
|
-
// src/app/pages/login/login.page.ts
|
|
1370
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, type OnInit, inject } from '@angular/core';
|
|
1371
|
-
import { ActivatedRoute, Router } from '@angular/router';
|
|
1372
|
-
import { StrivacityNativeLoginService } from '@strivacity/sdk-angular';
|
|
1373
|
-
|
|
1374
|
-
@Component({
|
|
1375
|
-
selector: 'app-login-page',
|
|
1376
|
-
template: `
|
|
1377
|
-
@if (nativeLoginService.loading() || !nativeLoginService.state().screen) {
|
|
1378
|
-
<section>
|
|
1379
|
-
<h1>Loading...</h1>
|
|
1380
|
-
</section>
|
|
1381
|
-
} @else if (nativeLoginService.state().screen === 'identifier') {
|
|
1382
|
-
<section>
|
|
1383
|
-
<h2>Sign In</h2>
|
|
1384
|
-
<form (submit)="onSubmitIdentifier($event)">
|
|
1385
|
-
<input
|
|
1386
|
-
type="text"
|
|
1387
|
-
placeholder="Email"
|
|
1388
|
-
[value]="nativeLoginService.forms()['identifier']?.['identifier'] ?? ''"
|
|
1389
|
-
(input)="nativeLoginService.setFormValue('identifier', 'identifier', $any($event.target).value)"
|
|
1390
|
-
/>
|
|
1391
|
-
@if (nativeLoginService.messages()['identifier']?.['identifier']; as message) {
|
|
1392
|
-
<div class="error">{{ message.text }}</div>
|
|
1393
|
-
}
|
|
1394
|
-
<button type="submit">Continue</button>
|
|
1395
|
-
</form>
|
|
1396
|
-
</section>
|
|
1397
|
-
} @else if (nativeLoginService.state().screen === 'password') {
|
|
1398
|
-
<section>
|
|
1399
|
-
<h2>Enter Password</h2>
|
|
1400
|
-
<form (submit)="onSubmitPassword($event)">
|
|
1401
|
-
<input
|
|
1402
|
-
type="password"
|
|
1403
|
-
placeholder="Password"
|
|
1404
|
-
[value]="nativeLoginService.forms()['password']?.['password'] ?? ''"
|
|
1405
|
-
(input)="nativeLoginService.setFormValue('password', 'password', $any($event.target).value)"
|
|
1406
|
-
/>
|
|
1407
|
-
@if (nativeLoginService.messages()['password']?.['password']; as message) {
|
|
1408
|
-
<div class="error">{{ message.text }}</div>
|
|
1409
|
-
}
|
|
1410
|
-
<button type="submit">Sign In</button>
|
|
1411
|
-
</form>
|
|
1412
|
-
</section>
|
|
1413
|
-
}
|
|
1414
|
-
`,
|
|
1415
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
1416
|
-
providers: [StrivacityNativeLoginService],
|
|
1417
|
-
})
|
|
1418
|
-
export class LoginPage implements OnInit {
|
|
1419
|
-
readonly nativeLoginService = inject(StrivacityNativeLoginService);
|
|
1420
|
-
private readonly router = inject(Router);
|
|
1421
|
-
private readonly route = inject(ActivatedRoute);
|
|
1422
|
-
|
|
1423
|
-
ngOnInit(): void {
|
|
1424
|
-
void this.nativeLoginService.start({
|
|
1425
|
-
params: {
|
|
1426
|
-
prompt: 'login', // use 'create' to open the registration flow instead
|
|
1427
|
-
language: 'en-US', // set the UI language (BCP 47 language tag)
|
|
1428
|
-
sdk: 'web-minimal', // rendering mode: 'web-minimal' for simplified rendering, 'web' (default) for full rendering hints and branding
|
|
1429
|
-
sessionId: this.route.snapshot.queryParamMap.get('session_id'), // pass a session ID to resume an existing flow
|
|
1430
|
-
},
|
|
1431
|
-
onLogin: async () => {
|
|
1432
|
-
await this.router.navigateByUrl('/profile');
|
|
1433
|
-
},
|
|
1434
|
-
onClose: () => {
|
|
1435
|
-
globalThis.location.reload();
|
|
1436
|
-
},
|
|
1437
|
-
onError: async (error) => {
|
|
1438
|
-
await this.router.navigate(['/error'], { queryParams: { message: error.message } });
|
|
1439
|
-
},
|
|
1440
|
-
onFallback: (error) => {
|
|
1441
|
-
// Fallback to hosted journey if native widget not supported
|
|
1442
|
-
globalThis.location.href = error.url.toString();
|
|
1443
|
-
},
|
|
1444
|
-
onGlobalMessage: (message) => {
|
|
1445
|
-
alert(message.text);
|
|
1446
|
-
},
|
|
1447
|
-
});
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
async onSubmitIdentifier(event: Event): Promise<void> {
|
|
1451
|
-
event.preventDefault();
|
|
1452
|
-
await this.nativeLoginService.submitForm('identifier');
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
async onSubmitPassword(event: Event): Promise<void> {
|
|
1456
|
-
event.preventDefault();
|
|
1457
|
-
await this.nativeLoginService.submitForm('password');
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
```
|
|
1461
|
-
|
|
1462
|
-
**Server-managed sessions**:
|
|
1463
|
-
|
|
1464
|
-
With `serverSessionUri` configured the code above is unchanged - `StrivacityNativeLoginService`'s internal requests are transparently proxied through your server instead of going straight to the IDP.
|
|
1465
|
-
|
|
1466
|
-
##### Handle the callback
|
|
1467
|
-
|
|
1468
|
-
**Client-managed sessions**:
|
|
1469
|
-
|
|
1470
|
-
No separate callback route is needed. Once `state().finalizeUrl` is set, `submitForm()` automatically finalizes the session internally to exchange the authorization code for tokens and store it.
|
|
1471
|
-
|
|
1472
|
-
**Server-managed sessions**:
|
|
1473
|
-
|
|
1474
|
-
Same as client managed - finalizing the session also transparently proxies through your server, with no separate route needed either way.
|
|
1475
|
-
|
|
1476
|
-
#### Externally-initiated flows (entry)
|
|
1477
|
-
|
|
1478
|
-
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`).
|
|
1479
|
-
|
|
1480
|
-
You have two options:
|
|
1481
|
-
|
|
1482
|
-
**Option 1: Redirect to a separate login route**
|
|
1483
|
-
|
|
1484
|
-
```ts
|
|
1485
|
-
// src/app/pages/entry/entry.page.ts
|
|
1486
|
-
import { Component, type OnInit, inject } from '@angular/core';
|
|
1487
|
-
import { Router } from '@angular/router';
|
|
1488
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
1489
|
-
import type { NativeFlow } from '@strivacity/sdk-angular/types';
|
|
1490
|
-
|
|
1491
|
-
@Component({
|
|
1492
|
-
selector: 'app-entry-page',
|
|
1493
|
-
template: `
|
|
1494
|
-
<section>
|
|
1495
|
-
<h1>Loading...</h1>
|
|
1496
|
-
</section>
|
|
1497
|
-
`,
|
|
1498
|
-
})
|
|
1499
|
-
export class EntryPage implements OnInit {
|
|
1500
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
1501
|
-
private readonly router = inject(Router);
|
|
1502
|
-
|
|
1503
|
-
ngOnInit(): void {
|
|
1504
|
-
void this.handleEntry();
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
private async handleEntry(): Promise<void> {
|
|
1508
|
-
try {
|
|
1509
|
-
const data = await (this.authService.sdk as NativeFlow).entry();
|
|
1510
|
-
const params = new URLSearchParams({
|
|
1511
|
-
session_id: data.session_id,
|
|
1512
|
-
short_app_id: data.short_app_id,
|
|
1513
|
-
language: data.language,
|
|
1514
|
-
});
|
|
1515
|
-
globalThis.location.href = `/login?${params}`;
|
|
1516
|
-
} catch (error) {
|
|
1517
|
-
await this.router.navigate(['/error'], { queryParams: { message: error instanceof Error ? error.message : 'Unknown error' } });
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
```
|
|
1522
|
-
|
|
1523
|
-
Then on your login route, read query parameters from the URL and pass it to `StrivacityNativeLoginService` to resume the flow, exactly as shown in the Login / Register example above:
|
|
1524
|
-
|
|
1525
|
-
```ts
|
|
1526
|
-
// src/app/pages/login/login.page.ts
|
|
1527
|
-
import { Component, type OnInit, inject } from '@angular/core';
|
|
1528
|
-
import { ActivatedRoute } from '@angular/router';
|
|
1529
|
-
import { StrivacityNativeLoginService } from '@strivacity/sdk-angular';
|
|
1530
|
-
|
|
1531
|
-
@Component({
|
|
1532
|
-
selector: 'app-login-page',
|
|
1533
|
-
template: '', // ...render based on `nativeLoginService.state().screen` as shown in the Login / Register example above
|
|
1534
|
-
providers: [StrivacityNativeLoginService],
|
|
1535
|
-
})
|
|
1536
|
-
export class LoginPage implements OnInit {
|
|
1537
|
-
readonly nativeLoginService = inject(StrivacityNativeLoginService);
|
|
1538
|
-
private readonly route = inject(ActivatedRoute);
|
|
1539
|
-
|
|
1540
|
-
ngOnInit(): void {
|
|
1541
|
-
void this.nativeLoginService.start({
|
|
1542
|
-
params: {
|
|
1543
|
-
sessionId: this.route.snapshot.queryParamMap.get('session_id'),
|
|
1544
|
-
language: this.route.snapshot.queryParamMap.get('language'),
|
|
1545
|
-
},
|
|
1546
|
-
onLogin: async () => {
|
|
1547
|
-
globalThis.location.href = '/profile';
|
|
1548
|
-
},
|
|
1549
|
-
});
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
```
|
|
1553
|
-
|
|
1554
|
-
**Option 2: Render login on the entry route**
|
|
1555
|
-
|
|
1556
|
-
Call `StrivacityNativeLoginService.start()` directly from `entry.page.ts`, feeding it the `session_id` resolved from `entry()` - no redirect needed:
|
|
1557
|
-
|
|
1558
|
-
```ts
|
|
1559
|
-
// src/app/pages/entry/entry.page.ts
|
|
1560
|
-
import { Component, type OnInit, inject } from '@angular/core';
|
|
1561
|
-
import { Router } from '@angular/router';
|
|
1562
|
-
import { StrivacityAuthService, StrivacityNativeLoginService } from '@strivacity/sdk-angular';
|
|
1563
|
-
import type { NativeFlow } from '@strivacity/sdk-angular/types';
|
|
1564
|
-
|
|
1565
|
-
@Component({
|
|
1566
|
-
selector: 'app-entry-page',
|
|
1567
|
-
template: `
|
|
1568
|
-
@if (nativeLoginService.loading() || !nativeLoginService.state().screen) {
|
|
1569
|
-
<section>
|
|
1570
|
-
<h1>Loading...</h1>
|
|
1571
|
-
</section>
|
|
1572
|
-
}
|
|
1573
|
-
<!-- ...render based on `nativeLoginService.state().screen` as shown in the Login / Register example above -->
|
|
1574
|
-
`,
|
|
1575
|
-
providers: [StrivacityNativeLoginService],
|
|
1576
|
-
})
|
|
1577
|
-
export class EntryPage implements OnInit {
|
|
1578
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
1579
|
-
readonly nativeLoginService = inject(StrivacityNativeLoginService);
|
|
1580
|
-
private readonly router = inject(Router);
|
|
1581
|
-
|
|
1582
|
-
ngOnInit(): void {
|
|
1583
|
-
void this.handleEntry();
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
private async handleEntry(): Promise<void> {
|
|
1587
|
-
try {
|
|
1588
|
-
const data = await (this.authService.sdk as NativeFlow).entry();
|
|
1589
|
-
|
|
1590
|
-
void this.nativeLoginService.start({
|
|
1591
|
-
params: { sessionId: data.session_id, language: data.language },
|
|
1592
|
-
onLogin: async () => {
|
|
1593
|
-
await this.router.navigateByUrl('/profile');
|
|
1594
|
-
},
|
|
1595
|
-
onError: async (error) => {
|
|
1596
|
-
await this.router.navigate(['/error'], { queryParams: { message: error.message } });
|
|
1597
|
-
},
|
|
1598
|
-
});
|
|
1599
|
-
} catch (error) {
|
|
1600
|
-
await this.router.navigate(['/error'], { queryParams: { message: error instanceof Error ? error.message : 'Unknown error' } });
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
```
|
|
1605
|
-
|
|
1606
|
-
##### Logout
|
|
1607
|
-
|
|
1608
|
-
**Client-managed sessions**:
|
|
1609
|
-
|
|
1610
|
-
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`.
|
|
1611
|
-
|
|
1612
|
-
```ts
|
|
1613
|
-
// src/app/pages/logout/logout.page.ts
|
|
1614
|
-
import { Component, type OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
1615
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
1616
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
1617
|
-
|
|
1618
|
-
@Component({
|
|
1619
|
-
selector: 'app-logout-page',
|
|
1620
|
-
template: `
|
|
1621
|
-
<section>
|
|
1622
|
-
<h1>Logging out...</h1>
|
|
1623
|
-
</section>
|
|
1624
|
-
`,
|
|
1625
|
-
})
|
|
1626
|
-
export class LogoutPage implements OnInit {
|
|
1627
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
1628
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
1629
|
-
|
|
1630
|
-
ngOnInit(): void {
|
|
1631
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
1632
|
-
void this.authService.logout();
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
```
|
|
1637
|
-
|
|
1638
|
-
**Server-managed sessions**:
|
|
1639
|
-
|
|
1640
|
-
With `serverSessionUri` configured, redirect to `/auth/logout` instead - the Server SDK clears the session and redirects to the IDP end-session endpoint:
|
|
1641
|
-
|
|
1642
|
-
```ts
|
|
1643
|
-
// src/app/pages/logout/logout.page.ts
|
|
1644
|
-
import { Component, type OnInit, PLATFORM_ID, RESPONSE_INIT, inject } from '@angular/core';
|
|
1645
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
1646
|
-
|
|
1647
|
-
@Component({
|
|
1648
|
-
selector: 'app-logout-page',
|
|
1649
|
-
template: `
|
|
1650
|
-
<section>
|
|
1651
|
-
<h1>Logging out...</h1>
|
|
1652
|
-
</section>
|
|
1653
|
-
`,
|
|
1654
|
-
})
|
|
1655
|
-
export class LogoutPage implements OnInit {
|
|
1656
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
1657
|
-
private readonly responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
1658
|
-
|
|
1659
|
-
ngOnInit(): void {
|
|
1660
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
1661
|
-
globalThis.location.href = '/auth/logout';
|
|
1662
|
-
} else if (this.responseInit) {
|
|
1663
|
-
this.responseInit.status = 302;
|
|
1664
|
-
this.responseInit.headers = new Headers({ Location: '/auth/logout' });
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
```
|
|
1669
|
-
|
|
1670
|
-
##### Token management
|
|
1671
|
-
|
|
1672
|
-
**Client-managed sessions**:
|
|
1673
|
-
|
|
1674
|
-
Call these methods to manage the session and access token client-side.
|
|
1675
|
-
|
|
1676
|
-
```ts
|
|
1677
|
-
import '@strivacity/common/components/token-field';
|
|
1678
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
|
|
1679
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
1680
|
-
|
|
1681
|
-
@Component({
|
|
1682
|
-
selector: 'app-token-panel',
|
|
1683
|
-
template: `
|
|
1684
|
-
@if (!authService.loading()) {
|
|
1685
|
-
<div>
|
|
1686
|
-
<button (click)="onRefresh()">Refresh</button>
|
|
1687
|
-
<button (click)="onRevoke()">Revoke</button>
|
|
1688
|
-
<pre>{{ { idTokenClaims: authService.idTokenClaims(), accessToken: authService.accessToken(), refreshToken: authService.refreshToken() } | json }}</pre>
|
|
1689
|
-
</div>
|
|
1690
|
-
}
|
|
1691
|
-
`,
|
|
1692
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
1693
|
-
})
|
|
1694
|
-
export class TokenPanelComponent {
|
|
1695
|
-
readonly authService = inject(StrivacityAuthService);
|
|
1696
|
-
|
|
1697
|
-
async onRefresh(): Promise<void> {
|
|
1698
|
-
// Refresh the access token using the refresh token
|
|
1699
|
-
await this.authService.refresh();
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
async onRevoke(): Promise<void> {
|
|
1703
|
-
// Revoke all tokens at the authorization server and clear the local session
|
|
1704
|
-
await this.authService.revoke();
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
```
|
|
1708
|
-
|
|
1709
|
-
**Server-managed sessions**:
|
|
1710
|
-
|
|
1711
|
-
With `serverSessionUri` configured, tokens are refreshed/revoked by the Server SDK - trigger it by navigating to the auth routes, then let the router redirect back:
|
|
1712
|
-
|
|
1713
|
-
```ts
|
|
1714
|
-
function onRefresh() {
|
|
1715
|
-
// sdk.refreshSession() runs server-side, then redirects back to returnTo
|
|
1716
|
-
globalThis.location.href = '/auth/refresh?returnTo=/profile';
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
function onRevoke() {
|
|
1720
|
-
// sdk.revokeSession() runs server-side, then redirects to postLogoutRedirectUri
|
|
1721
|
-
globalThis.location.href = '/auth/revoke';
|
|
1722
|
-
}
|
|
1723
|
-
```
|
|
1724
|
-
|
|
1725
|
-
---
|
|
1726
|
-
|
|
1727
|
-
### Services API
|
|
1728
|
-
|
|
1729
|
-
#### StrivacityAuthService
|
|
1730
|
-
|
|
1731
|
-
The main service for accessing the SDK instance and reactive session state. Provided via `provideStrivacity()`/`StrivacityAuthModule.forRoot()` (see [Quick start](#quick-start)) - just `inject()` it wherever you need it.
|
|
1732
|
-
|
|
1733
|
-
```ts
|
|
1734
|
-
import { Component, inject } from '@angular/core';
|
|
1735
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
1736
|
-
|
|
1737
|
-
@Component({ /* ... */ })
|
|
1738
|
-
export class SomeComponent {
|
|
1739
|
-
private readonly authService = inject(StrivacityAuthService);
|
|
1740
|
-
}
|
|
1741
|
-
```
|
|
1742
|
-
|
|
1743
|
-
##### Members
|
|
1744
|
-
|
|
1745
|
-
```ts
|
|
1746
|
-
{
|
|
1747
|
-
// SDK instance (access any SDK method)
|
|
1748
|
-
readonly sdk: RedirectFlow | PopupFlow | EmbeddedFlow | NativeFlow;
|
|
1749
|
-
|
|
1750
|
-
// Reactive state (signals - call them, e.g. `authService.loading()`; templates re-render when they change)
|
|
1751
|
-
readonly loading: Signal<boolean>; // True during initialization
|
|
1752
|
-
readonly language: Signal<string>; // Current BCP 47 language code
|
|
1753
|
-
readonly isAuthenticated: Signal<boolean>; // True if user has valid session
|
|
1754
|
-
readonly idTokenClaims: Signal<IdTokenClaims | null>; // Decoded ID token claims
|
|
1755
|
-
readonly accessToken: Signal<string | null>; // Current access token
|
|
1756
|
-
readonly refreshToken: Signal<string | null>; // Current refresh token
|
|
1757
|
-
readonly accessTokenExpired: Signal<boolean>; // True once the access token has expired
|
|
1758
|
-
readonly accessTokenExpirationDate: Signal<number | null>; // Access token expiration timestamp
|
|
1759
|
-
|
|
1760
|
-
// Methods (all are async)
|
|
1761
|
-
login(params?: LoginParams): Promise<void>; // Start login flow
|
|
1762
|
-
register(params?: LoginParams): Promise<void>; // Start registration flow
|
|
1763
|
-
handleCallback(url?: string): Promise<void>; // Handle OAuth callback
|
|
1764
|
-
logout(params?: LogoutParams): Promise<void>; // End session
|
|
1765
|
-
refresh(): Promise<void>; // Refresh access token
|
|
1766
|
-
revoke(): Promise<void>; // Revoke tokens
|
|
1767
|
-
entry(): Promise<EntryData>; // Handle external entry (embedded/native only)
|
|
1768
|
-
}
|
|
1769
|
-
```
|
|
1770
|
-
|
|
1771
|
-
#### StrivacityNativeLoginService
|
|
1772
|
-
|
|
1773
|
-
Service for managing native login flow state. Only available in `native` mode. Unlike `StrivacityAuthService`, it must be provided per-component (`providers: [StrivacityNativeLoginService]` on the component that owns the flow) and started explicitly by calling `start(options)` (e.g. from `ngOnInit`) - see [native mode](#native-mode) above.
|
|
1774
|
-
|
|
1775
|
-
```ts
|
|
1776
|
-
import { Component, type OnInit, inject } from '@angular/core';
|
|
1777
|
-
import { StrivacityNativeLoginService } from '@strivacity/sdk-angular';
|
|
1778
|
-
|
|
1779
|
-
@Component({ /* ... */ providers: [StrivacityNativeLoginService] })
|
|
1780
|
-
export class SomeComponent implements OnInit {
|
|
1781
|
-
private readonly nativeLoginService = inject(StrivacityNativeLoginService);
|
|
1782
|
-
|
|
1783
|
-
ngOnInit(): void {
|
|
1784
|
-
void this.nativeLoginService.start({
|
|
1785
|
-
params: { /* login params */ },
|
|
1786
|
-
onLogin: (session) => { /* handle login */ },
|
|
1787
|
-
onError: (error) => { /* handle error */ },
|
|
1788
|
-
// ... other callbacks
|
|
1789
|
-
});
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
```
|
|
1793
|
-
|
|
1794
|
-
##### start() options
|
|
1795
|
-
|
|
1796
|
-
```ts
|
|
1797
|
-
{
|
|
1798
|
-
params?: NativeParams; // Initial flow parameters
|
|
1799
|
-
onLogin?: (session: SessionData) => void | Promise<void>; // Called on successful login
|
|
1800
|
-
onClose?: () => void; // Called when user closes the flow
|
|
1801
|
-
onError?: (error: unknown) => void; // Called on error
|
|
1802
|
-
onFallback?: (error: FallbackError) => void; // Called when fallback needed
|
|
1803
|
-
onGlobalMessage?: (message: NativeFlowMessage) => void; // Called for global messages
|
|
1804
|
-
}
|
|
1805
|
-
```
|
|
1806
|
-
|
|
1807
|
-
##### Members
|
|
1808
|
-
|
|
1809
|
-
```ts
|
|
1810
|
-
{
|
|
1811
|
-
// Reactive state (signals - call them, e.g. `nativeLoginService.loading()`; templates re-render when they change)
|
|
1812
|
-
loading: Signal<boolean>; // True while fetching next screen
|
|
1813
|
-
state: Signal<Partial<NativeFlowState>>; // Current flow state (screen, forms, layout, etc.)
|
|
1814
|
-
forms: Signal<Record<string, Record<string, unknown>>>; // Form data by form ID
|
|
1815
|
-
messages: Signal<Record<string, Record<string, NativeFlowMessage>>>; // Validation messages
|
|
1816
|
-
|
|
1817
|
-
// Methods
|
|
1818
|
-
start(options?: NativeLoginOptions): Promise<void>; // Start the native login flow - see options above
|
|
1819
|
-
submitForm(formId: string, customBody?: Record<string, unknown>): Promise<void>; // Submit a form and advance to next screen
|
|
1820
|
-
setFormValue(formId: string, widgetId: string, value: unknown): void; // Update a single field value before submission
|
|
1821
|
-
setMessage(formId: string, widgetId: string, value: NativeFlowMessage): void; // Set a validation/info message on a widget
|
|
1822
|
-
triggerFallback(message?: string): void; // Manually trigger fallback to hosted journey
|
|
1823
|
-
triggerClose(): void; // Signal that the login flow was closed by the user
|
|
1824
|
-
}
|
|
1825
|
-
```
|
|
1826
|
-
|
|
1827
|
-
---
|
|
1828
|
-
|
|
1829
|
-
## Server SDK
|
|
1830
|
-
|
|
1831
|
-
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 Angular: `createServerSDK` provides an Express-based `ServerAdapter` and a default encrypted-cookie storage.
|
|
1832
|
-
|
|
1833
|
-
### Setup
|
|
1834
|
-
|
|
1835
|
-
```ts
|
|
1836
|
-
// src/server/strivacity.ts
|
|
1837
|
-
import { createServerSDK } from '@strivacity/sdk-angular/server';
|
|
1838
|
-
import { sdkOptions } from '../options';
|
|
1839
|
-
|
|
1840
|
-
export const serverSdk = createServerSDK({
|
|
1841
|
-
...sdkOptions,
|
|
1842
|
-
secret: process.env.SECRET, // required unless you provide a custom `storage`
|
|
1843
|
-
postLoginRedirectUri: '/profile',
|
|
1844
|
-
});
|
|
1845
|
-
```
|
|
1846
|
-
|
|
1847
|
-
Mount the Express router once - see [Quick start](#quick-start) for the full `server.ts`/`app.config.server.ts` wiring:
|
|
1848
|
-
|
|
1849
|
-
```ts
|
|
1850
|
-
// src/server/server.ts
|
|
1851
|
-
import express from 'express';
|
|
1852
|
-
import { sdkOptions } from '../options';
|
|
1853
|
-
import { serverSdk } from './strivacity';
|
|
1854
|
-
|
|
1855
|
-
const app = express();
|
|
1856
|
-
|
|
1857
|
-
app.use(sdkOptions.authUrlPrefix!, serverSdk.handlers);
|
|
1858
|
-
```
|
|
1859
|
-
|
|
1860
|
-
### Accessing the session server-side
|
|
1861
|
-
|
|
1862
|
-
Call `getSession(req)` from any Express route handler, or from `provideStrivacityServerSession()`'s own `provideAppInitializer` (already wired up in [Quick start](#quick-start)), to read the current session without going through the client SDK:
|
|
1863
|
-
|
|
1864
|
-
```ts
|
|
1865
|
-
// src/server/session.ts
|
|
1866
|
-
import { serverSdk } from './strivacity';
|
|
1867
|
-
|
|
1868
|
-
app.get('/api/me', async (req, res) => {
|
|
1869
|
-
const session = await serverSdk.getSession(req);
|
|
1870
|
-
res.json({ claims: session?.claims });
|
|
1871
|
-
});
|
|
1872
|
-
```
|
|
1873
|
-
|
|
1874
|
-
Angular's own `REQUEST` token (populated by `@angular/ssr` during SSR) works the same way - `provideStrivacityServerSession()` (see [Quick start](#quick-start)) already uses it to hydrate `TransferState` before the app renders:
|
|
1875
|
-
|
|
1876
|
-
```ts
|
|
1877
|
-
import { REQUEST, inject } from '@angular/core';
|
|
1878
|
-
import { serverSdk } from './strivacity';
|
|
1879
|
-
|
|
1880
|
-
const session = await serverSdk.getSession(inject(REQUEST) ?? undefined);
|
|
1881
|
-
```
|
|
1882
|
-
|
|
1883
|
-
<a id="server-storages"></a>
|
|
1884
|
-
### Storages
|
|
1885
|
-
|
|
1886
|
-
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.
|
|
1887
|
-
|
|
1888
|
-
#### Built-in session storages
|
|
1889
|
-
|
|
1890
|
-
- **`createEncryptedCookieStorage(secret, options?)`** - default storage that keeps the session encrypted in an http-only cookie.
|
|
1891
|
-
- **`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.
|
|
1892
|
-
|
|
1893
|
-
```ts
|
|
1894
|
-
// src/server/storage.ts
|
|
1895
|
-
import { createSessionIdCookieStorage, createServerMemoryStorage } from '@strivacity/sdk-angular';
|
|
1896
|
-
|
|
1897
|
-
export const sessionStorage = createSessionIdCookieStorage(
|
|
1898
|
-
createServerMemoryStorage(),
|
|
1899
|
-
{
|
|
1900
|
-
// maxAge: 30 * 24 * 60 * 60 // Without maxAge this is a browser-session cookie that gets cleared when the browser closes
|
|
1901
|
-
},
|
|
1902
|
-
);
|
|
1903
|
-
```
|
|
1904
|
-
|
|
1905
|
-
```ts
|
|
1906
|
-
// src/server/strivacity.ts
|
|
1907
|
-
import { createServerSDK } from '@strivacity/sdk-angular/server';
|
|
1908
|
-
import { sdkOptions } from '../options';
|
|
1909
|
-
import { sessionStorage } from './storage';
|
|
1910
|
-
|
|
1911
|
-
export const serverSdk = createServerSDK({
|
|
1912
|
-
...sdkOptions,
|
|
1913
|
-
storage: sessionStorage,
|
|
1914
|
-
postLoginRedirectUri: '/profile',
|
|
1915
|
-
});
|
|
1916
|
-
```
|
|
1917
|
-
|
|
1918
|
-
#### Custom storage
|
|
1919
|
-
|
|
1920
|
-
For example you can use Redis via [unstorage](https://npmjs.com/package/unstorage):
|
|
1921
|
-
|
|
1922
|
-
```ts
|
|
1923
|
-
// src/server/storage.ts
|
|
1924
|
-
import { createStorage } from 'unstorage';
|
|
1925
|
-
import redisDriver from 'unstorage/drivers/redis';
|
|
1926
|
-
import type { AngularServerStorage, SDKStorage } from '@strivacity/sdk-angular/server';
|
|
1927
|
-
|
|
1928
|
-
const unstorageInstance = createStorage({ driver: redisDriver({ url: process.env.REDIS_URL }) });
|
|
1929
|
-
|
|
1930
|
-
// Custom session storage for tokens
|
|
1931
|
-
export const sessionStorage: AngularServerStorage = {
|
|
1932
|
-
async get(key) {
|
|
1933
|
-
return unstorageInstance.getItem<string>(key);
|
|
1934
|
-
},
|
|
1935
|
-
async set(key, value) {
|
|
1936
|
-
await unstorageInstance.setItem(key, value);
|
|
1937
|
-
},
|
|
1938
|
-
async delete(key) {
|
|
1939
|
-
await unstorageInstance.removeItem(key);
|
|
1940
|
-
},
|
|
1941
|
-
// Required for back-channel logout support - see below.
|
|
1942
|
-
// Scans all stored sessions and removes those matching the logout token's sid or sub claim.
|
|
1943
|
-
async deleteByLogoutToken(logoutToken) {
|
|
1944
|
-
const keys = await unstorageInstance.getKeys();
|
|
1945
|
-
await Promise.all(
|
|
1946
|
-
keys.map(async (key) => {
|
|
1947
|
-
const raw = await unstorageInstance.getItem<string>(key);
|
|
1948
|
-
if (!raw) return;
|
|
1949
|
-
const session = JSON.parse(raw);
|
|
1950
|
-
if ((logoutToken.sid && session.sid === logoutToken.sid) || (logoutToken.sub && session.sub === logoutToken.sub)) {
|
|
1951
|
-
await unstorageInstance.removeItem(key);
|
|
1952
|
-
}
|
|
1953
|
-
}),
|
|
1954
|
-
);
|
|
1955
|
-
},
|
|
1956
|
-
};
|
|
1957
|
-
|
|
1958
|
-
// Custom state storage for the OAuth2 state parameter
|
|
1959
|
-
export const stateStorage: SDKStorage = {
|
|
1960
|
-
async get(key) {
|
|
1961
|
-
return unstorageInstance.getItem<string>(key);
|
|
1962
|
-
},
|
|
1963
|
-
async set(key, value) {
|
|
1964
|
-
await unstorageInstance.setItem(key, value);
|
|
1965
|
-
},
|
|
1966
|
-
async delete(key) {
|
|
1967
|
-
await unstorageInstance.removeItem(key);
|
|
1968
|
-
},
|
|
1969
|
-
};
|
|
1970
|
-
```
|
|
1971
|
-
|
|
1972
|
-
```ts
|
|
1973
|
-
// src/server/strivacity.ts
|
|
1974
|
-
import { createServerSDK } from '@strivacity/sdk-angular/server';
|
|
1975
|
-
import { sdkOptions } from '../options';
|
|
1976
|
-
import { sessionStorage, stateStorage } from './storage';
|
|
1977
|
-
|
|
1978
|
-
export const serverSdk = createServerSDK({
|
|
1979
|
-
...sdkOptions,
|
|
1980
|
-
storage: sessionStorage, // Custom Redis-backed session storage
|
|
1981
|
-
stateStorage, // Custom Redis-backed state storage
|
|
1982
|
-
});
|
|
1983
|
-
```
|
|
1984
|
-
|
|
1985
|
-
> For more details on the storage interfaces, see the core SDK's [Custom storage](../sdk-core/README.md#server-storages) section.
|
|
1986
|
-
|
|
1987
|
-
### Back-channel logout
|
|
1988
|
-
|
|
1989
|
-
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 Express router from [Setup](#setup) - which routes it to `sdk.handleBackChannelLogout(req)`.
|
|
1990
|
-
|
|
1991
|
-
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`.
|
|
1992
|
-
|
|
1993
|
-
> **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.
|
|
1994
|
-
|
|
1995
|
-
Configure the **Back-channel logout URI** in your Strivacity application settings to:
|
|
1996
|
-
|
|
1997
|
-
```
|
|
1998
|
-
https://your-app.example.com/auth/backchannel-logout
|
|
1999
|
-
```
|
|
2000
|
-
|
|
2001
|
-
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.
|
|
2002
|
-
|
|
2003
|
-
### Server SDK API reference
|
|
2004
|
-
|
|
2005
|
-
```ts
|
|
2006
|
-
{
|
|
2007
|
-
options: ServerSDKOptions; // resolved server SDK configuration
|
|
2008
|
-
|
|
2009
|
-
// Session management
|
|
2010
|
-
getSession(req?): Promise<SessionData | null>; // read the current session
|
|
2011
|
-
updateSession(session, req?): Promise<void>; // persist new session data
|
|
2012
|
-
refreshSession(req?): Promise<SessionData>; // refresh tokens using the refresh token
|
|
2013
|
-
revokeSession(req?): Promise<void>; // revoke tokens and clear the session
|
|
2014
|
-
getEntrySession(entryUrl): Promise<Record<string, string>>; // resolve an externally-initiated (embedded/native) entry URL
|
|
2015
|
-
completeLogin(params, req?): Promise<SessionData>; // exchange an authorization code for tokens
|
|
2016
|
-
logout(postLogoutRedirectUri, req?): Promise<URL>; // clear the session, returns the IDP end-session URL
|
|
2017
|
-
|
|
2018
|
-
// Route handlers - each returns a Response; `handler` dispatches to the one matching the request path
|
|
2019
|
-
handleLogin(req): Promise<Response>;
|
|
2020
|
-
handleRegister(req): Promise<Response>;
|
|
2021
|
-
handleCallback(req): Promise<Response>;
|
|
2022
|
-
handleRefresh(req): Promise<Response>;
|
|
2023
|
-
handleRevoke(req): Promise<Response>;
|
|
2024
|
-
handleEntry(req): Promise<Response>;
|
|
2025
|
-
handleLogout(req): Promise<Response>;
|
|
2026
|
-
handleBackChannelLogout(req): Promise<Response>;
|
|
2027
|
-
handler(req): Promise<Response | null>; // dispatches based on `authUrlPrefix`, or null if no route matched
|
|
2028
|
-
|
|
2029
|
-
// Angular-specific
|
|
2030
|
-
readonly handlers: Router; // the Express router exposing the auth endpoints; see Setup
|
|
2031
|
-
}
|
|
2032
|
-
```
|
|
2033
|
-
|
|
2034
|
-
### Server configuration reference
|
|
2035
|
-
|
|
2036
|
-
The Server SDK accepts the same configuration as the client SDK (see [Configuration reference](#configuration-reference)), plus:
|
|
2037
|
-
|
|
2038
|
-
| Option | Type | Required | Default | Description |
|
|
2039
|
-
| ------ | ---- | -------- | ------- | ----------- |
|
|
2040
|
-
| `secret` | `string` | Only if using default storage | - | Encryption key (32+ random characters) for the http-only cookie session storage |
|
|
2041
|
-
| `storage` | `AngularServerStorage` | No | Encrypted cookie storage | Custom session storage; see [Storages](#server-storages) |
|
|
2042
|
-
| `stateStorage` | `SDKStorage` | No | In-memory `Map` | Custom OAuth2 state storage |
|
|
2043
|
-
| `authUrlPrefix` | `string` | No | `'/auth'` | URL prefix matched by `sdk.handlers` |
|
|
2044
|
-
| `loginUri` | `string` | No | `'/login'` | Route your own [route guard](#route-guards) redirects to when there's no session |
|
|
2045
|
-
| `postLoginRedirectUri` | `string` | No | - | Default redirect after login when no `?returnTo=` is given |
|
|
2046
|
-
| `postLogoutRedirectUri` | `string` | No | - | Default redirect after logout |
|
|
2047
|
-
| `cookieMaxAge` | `number` | No | `2592000` (30 days) | Max age of the session cookie in seconds |
|
|
2048
|
-
|
|
2049
|
-
---
|
|
2050
|
-
|
|
2051
|
-
## Route guards
|
|
2052
|
-
|
|
2053
|
-
This SDK doesn't ship a route-guard helper - use a plain Angular `CanActivateFn` with `StrivacityAuthService` and `RedirectCommand`. The same guard works both for client-side navigation (the Router performs the redirect) and during SSR (`RedirectCommand` produces a genuine HTTP redirect via Angular's SSR pipeline) - there's no separate client/server variant to write:
|
|
2054
|
-
|
|
2055
|
-
```ts
|
|
2056
|
-
// src/app/guards/auth.guard.ts
|
|
2057
|
-
import type { CanActivateFn } from '@angular/router';
|
|
2058
|
-
import { inject } from '@angular/core';
|
|
2059
|
-
import { RedirectCommand, Router } from '@angular/router';
|
|
2060
|
-
import { StrivacityAuthService } from '@strivacity/sdk-angular';
|
|
2061
|
-
|
|
2062
|
-
export const authGuard: CanActivateFn = async () => {
|
|
2063
|
-
const authService = inject(StrivacityAuthService);
|
|
2064
|
-
const router = inject(Router);
|
|
2065
|
-
|
|
2066
|
-
await authService.init();
|
|
2067
|
-
|
|
2068
|
-
if (await authService.sdk.isAuthenticated) {
|
|
2069
|
-
return true;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
return new RedirectCommand(router.parseUrl('/login'));
|
|
2073
|
-
};
|
|
2074
|
-
```
|
|
2075
|
-
|
|
2076
|
-
```ts
|
|
2077
|
-
// src/app/app.routes.ts
|
|
2078
|
-
import type { Routes } from '@angular/router';
|
|
2079
|
-
import { ProfilePage } from './pages/profile';
|
|
2080
|
-
import { authGuard } from './guards/auth.guard';
|
|
2081
|
-
|
|
2082
|
-
export const routes: Routes = [{ path: 'profile', component: ProfilePage, canActivate: [authGuard] }];
|
|
2083
|
-
```
|
|
2084
|
-
|
|
2085
|
-
---
|
|
2086
|
-
|
|
2087
|
-
## Shared features
|
|
2088
|
-
|
|
2089
|
-
The Angular SDK is built on top of the core SDK and supports all its features, on both the client and server:
|
|
2090
|
-
|
|
2091
|
-
- **[Storages](../sdk-core/README.md#storages)** - localStorage, sessionStorage, IndexedDB, Cache API, Memory, Worker (client), encrypted cookies, in-memory (server)
|
|
2092
|
-
- **[SDK events](../sdk-core/README.md#sdk-events)** - Subscribe to authentication lifecycle events
|
|
2093
|
-
- **[Logging](../sdk-core/README.md#logging)** - Built-in and custom logger support
|
|
2094
|
-
- **[HTTP client](../sdk-core/README.md#http-client)** - Custom HTTP client integration
|
|
2095
|
-
- **[Error handling](../sdk-core/README.md#error-handling)** - Typed error classes for different failure scenarios
|
|
2096
|
-
- **[Utility functions](../sdk-core/README.md#utility-functions)** - Base64URL, JWT decoding, encryption, etc.
|
|
2097
|
-
- **[Caching](../sdk-core/README.md#caching)** - OIDC metadata and JWKS caching
|
|
2098
|
-
|
|
2099
|
-
---
|
|
2100
|
-
|
|
2101
|
-
## Configuration reference
|
|
2102
|
-
|
|
2103
|
-
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.
|
|
2104
|
-
|
|
2105
|
-
---
|
|
2106
|
-
|
|
2107
|
-
## Migration guide
|
|
2108
|
-
|
|
2109
|
-
### Migrating to v4.0
|
|
2110
|
-
|
|
2111
|
-
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. `StrivacityAuthService`, `provideStrivacity()`, 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.
|
|
2112
|
-
|
|
2113
|
-
#### Class-based flows replaced by functions
|
|
2114
|
-
|
|
2115
|
-
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:
|
|
2116
|
-
|
|
2117
|
-
```ts
|
|
2118
|
-
// v3
|
|
2119
|
-
import { NativeFlow } from '@strivacity/sdk-core/flows/NativeFlow';
|
|
2120
|
-
|
|
2121
|
-
export class CustomNativeFlow extends NativeFlow {
|
|
2122
|
-
override async refresh(): Promise<void> {
|
|
2123
|
-
// ...
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
```
|
|
2127
|
-
|
|
2128
|
-
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:
|
|
2129
|
-
|
|
2130
|
-
```ts
|
|
2131
|
-
// v4
|
|
2132
|
-
import { createBaseFlow } from '@strivacity/sdk-core/flows/base';
|
|
2133
|
-
import { getDefaultFlowState, getSDKOptions } from '@strivacity/sdk-core/utils';
|
|
2134
|
-
import type { SDKInitConfig, SDKOptions } from '@strivacity/sdk-core/types';
|
|
2135
|
-
|
|
2136
|
-
export function createCustomFlow(initConfig: SDKInitConfig) {
|
|
2137
|
-
const state = getDefaultFlowState();
|
|
2138
|
-
const options = getSDKOptions<SDKOptions>(state, initConfig);
|
|
2139
|
-
const base = createBaseFlow(state, options);
|
|
2140
|
-
|
|
2141
|
-
async function refresh(): Promise<void> {
|
|
2142
|
-
// ...
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
return { ...base, refresh };
|
|
2146
|
-
}
|
|
2147
|
-
```
|
|
2148
|
-
|
|
2149
|
-
This is a low-level `@strivacity/sdk-core` primitive - it's used the same way no matter which framework package you build on top of it. Wire it up by adding `factory: createCustomFlow` to the `sdkOptions` object passed to `provideStrivacity()` - see [Custom flow](../sdk-core/README.md#custom-flow) in the core SDK README for the full pattern and usage example.
|
|
2150
|
-
|
|
2151
|
-
#### Server-managed sessions ([BFF](../../README.md#bff)) are now built in
|
|
2152
|
-
|
|
2153
|
-
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.
|
|
2154
|
-
|
|
2155
|
-
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-angular/server`, and mount its `handlers` Express router - PKCE, state, and session storage are all handled by the Server SDK:
|
|
2156
|
-
|
|
2157
|
-
```ts
|
|
2158
|
-
// src/server/strivacity.ts
|
|
2159
|
-
import { createServerSDK } from '@strivacity/sdk-angular/server';
|
|
2160
|
-
import { sdkOptions } from '../options';
|
|
2161
|
-
|
|
2162
|
-
export const serverSdk = createServerSDK({
|
|
2163
|
-
...sdkOptions,
|
|
2164
|
-
secret: process.env.SECRET, // http-only cookie encryption key (random 32+ characters)
|
|
2165
|
-
});
|
|
2166
|
-
```
|
|
2167
|
-
|
|
2168
|
-
#### Native mode: no more `NativeFlowHandler`
|
|
2169
|
-
|
|
2170
|
-
In v3, `native` mode's `login()`/`register()` returned a separate `NativeFlowHandler` instance, and the flow was driven through that handler:
|
|
2171
|
-
|
|
2172
|
-
```ts
|
|
2173
|
-
// v3
|
|
2174
|
-
const handler = await sdk.login();
|
|
2175
|
-
const state = await handler.startSession(sessionId);
|
|
2176
|
-
const nextState = await handler.submitForm('formId', { identifier: 'user@example.com' });
|
|
2177
|
-
await handler.finalizeSession(nextState.finalizeUrl);
|
|
2178
|
-
```
|
|
2179
|
-
|
|
2180
|
-
v4 moves `startSession()`, `submitForm()`, and `finalizeSession()` directly onto the flow itself - in `@strivacity/sdk-angular` this is wrapped for you by [`StrivacityNativeLoginService`](#strivacitynativeloginservice):
|
|
2181
|
-
|
|
2182
|
-
```ts
|
|
2183
|
-
import { Component, inject } from '@angular/core';
|
|
2184
|
-
import { StrivacityNativeLoginService } from '@strivacity/sdk-angular';
|
|
2185
|
-
|
|
2186
|
-
// v4
|
|
2187
|
-
@Component({ /* ... */ providers: [StrivacityNativeLoginService] })
|
|
2188
|
-
export class LoginPage {
|
|
2189
|
-
readonly nativeLoginService = inject(StrivacityNativeLoginService);
|
|
2190
|
-
|
|
2191
|
-
async ngOnInit() {
|
|
2192
|
-
await this.nativeLoginService.start({ sessionId });
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
```
|
|
2196
|
-
|
|
2197
|
-
Update any code that calls `login()`/`register()` and drives the returned handler in `native` mode to use `StrivacityNativeLoginService` instead.
|
|
2198
|
-
|
|
2199
|
-
## Migrating to v3.0
|
|
2200
|
-
|
|
2201
|
-
### Entry API Major Changes
|
|
2202
|
-
|
|
2203
|
-
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.
|
|
2204
|
-
|
|
2205
|
-
---
|
|
2206
|
-
|
|
2207
|
-
## Vulnerability Reporting
|
|
2208
|
-
|
|
2209
|
-
The [Guidelines for responsible disclosure](https://www.strivacity.com/report-a-security-issue) details the procedure for disclosing security issues. Please do not report security vulnerabilities on the public issue tracker.
|
|
2210
|
-
|
|
2211
|
-
## License
|
|
2212
|
-
|
|
2213
|
-
This package is available under the MIT License. See the [LICENSE](https://github.com/Strivacity/sdk-js/blob/main/LICENSE) file for more info.
|
|
2214
|
-
|
|
2215
|
-
## Contributing
|
|
2216
|
-
|
|
2217
|
-
Please see our [contributing guide](https://github.com/Strivacity/sdk-js/blob/main/CONTRIBUTING.md).
|