@darkauth/client 1.22.1 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,7 +19,7 @@ The client supports both:
19
19
  ## Installation
20
20
 
21
21
  ```bash
22
- npm install @DarkAuth/client
22
+ pnpm install @DarkAuth/client
23
23
  ```
24
24
 
25
25
  ## Quick Start
@@ -72,7 +72,8 @@ setConfig({
72
72
  tokenStorage: 'memory', // Optional. Default 'memory'. Use 'localStorage' only for legacy flows.
73
73
  drkStorage: 'memory', // Optional. Default 'memory'. Use 'localStorage' only for explicit convenience mode.
74
74
  refreshMode: 'cookie', // Optional. Default 'cookie'. Use 'token' only for legacy refresh-token clients.
75
- credentials: 'include' // Optional. Default 'include' for cookie refresh.
75
+ credentials: 'include', // Optional. Default 'include' for cookie refresh.
76
+ endSessionEndpoint: 'https://auth.example.com/api/logout' // Optional. Override the RP-initiated logout endpoint (otherwise read from discovery).
76
77
  });
77
78
  ```
78
79
 
@@ -106,7 +107,28 @@ Behavior:
106
107
 
107
108
  #### `logout(): void`
108
109
 
109
- Clears the in-memory session, callback state, PKCE verifier, ephemeral ZK key, and any explicitly configured legacy storage.
110
+ Clears the in-memory session, callback state, PKCE verifier, ephemeral ZK key, and any explicitly configured legacy storage. This is a **local-only** logout — it does not end the DarkAuth SSO session, so a subsequent `initiateLogin()` may silently re-authenticate. Use `endSession()` when you need to end the IdP session too.
111
+
112
+ #### `endSession(options?: EndSessionOptions): Promise<void>`
113
+
114
+ Performs OIDC [RP-initiated logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html). Clears the local session (same as `logout()`) and then redirects the browser to DarkAuth's `end_session_endpoint`, which ends the SSO session and redirects back to your app.
115
+
116
+ ```typescript
117
+ import { endSession } from '@DarkAuth/client';
118
+
119
+ await endSession({
120
+ postLogoutRedirectUri: `${window.location.origin}/login`,
121
+ state: 'optional-csrf-value',
122
+ });
123
+ ```
124
+
125
+ Options (`EndSessionOptions`):
126
+ - `postLogoutRedirectUri?`: Where DarkAuth redirects after ending the session. **Must be registered in the client's `post_logout_redirect_uris` allowlist** on the DarkAuth server (exact match), otherwise the request is rejected. When provided, the SDK also sends `client_id` (the configured `clientId` unless overridden).
127
+ - `state?`: Opaque value echoed back on the post-logout redirect (use it to protect against CSRF / restore app state).
128
+ - `idTokenHint?`: The ID token to send as `id_token_hint`. Defaults to the current session's ID token. With a valid hint DarkAuth ends the session without a confirmation prompt.
129
+ - `clientId?`: Override the `client_id` sent alongside `post_logout_redirect_uri` (defaults to the configured `clientId`).
130
+
131
+ The `end_session_endpoint` is resolved from the server's `/.well-known/openid-configuration` discovery document, falling back to `<issuer>/api/logout`, or to the `endSessionEndpoint` config value when set.
110
132
 
111
133
  #### `getStoredSession(): AuthSession | null`
112
134
 
@@ -300,6 +322,17 @@ type Config = {
300
322
  drkStorage?: 'memory' | 'localStorage';
301
323
  refreshMode?: 'cookie' | 'token';
302
324
  credentials?: RequestCredentials;
325
+ endSessionEndpoint?: string;
326
+ }
327
+ ```
328
+
329
+ ### `EndSessionOptions`
330
+ ```typescript
331
+ type EndSessionOptions = {
332
+ postLogoutRedirectUri?: string;
333
+ state?: string;
334
+ idTokenHint?: string;
335
+ clientId?: string;
303
336
  }
304
337
  ```
305
338
 
@@ -367,17 +400,17 @@ This library requires a modern browser with support for:
367
400
 
368
401
  ```bash
369
402
  # Install dependencies
370
- npm install
403
+ pnpm install
371
404
 
372
405
  # Build the package
373
- npm run build
406
+ pnpm build
374
407
 
375
408
  # Type checking
376
- npm run typecheck
409
+ pnpm typecheck
377
410
 
378
411
  # Linting and formatting
379
- npm run lint
380
- npm run format
412
+ pnpm lint
413
+ pnpm format
381
414
  ```
382
415
 
383
416
  ## License
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ type Config = {
9
9
  zk?: boolean;
10
10
  authorizationEndpoint?: string;
11
11
  tokenEndpoint?: string;
12
+ endSessionEndpoint?: string;
12
13
  discovery?: boolean;
13
14
  firstParty?: boolean;
14
15
  tokenStorage?: "memory" | "localStorage";
@@ -60,6 +61,12 @@ export type SwitchOrganizationOptions = {
60
61
  export type RefreshSessionOptions = {
61
62
  force?: boolean;
62
63
  };
64
+ export type EndSessionOptions = {
65
+ postLogoutRedirectUri?: string;
66
+ state?: string;
67
+ idTokenHint?: string;
68
+ clientId?: string;
69
+ };
63
70
  export type DarkAuthSessionInfo = {
64
71
  authenticated: boolean;
65
72
  sub?: string;
@@ -94,4 +101,5 @@ export declare function listOrganizations(): Promise<DarkAuthOrganization[]>;
94
101
  export declare function getSessionInfo(): Promise<DarkAuthSessionInfo>;
95
102
  export declare function switchOrganization(organizationId: string, options?: SwitchOrganizationOptions): Promise<AuthSession | null>;
96
103
  export declare function logout(): void;
104
+ export declare function endSession(options?: EndSessionOptions): Promise<void>;
97
105
  export declare function getCurrentUser(): JwtClaims | null;
package/dist/index.js CHANGED
@@ -155,6 +155,7 @@ async function resolveEndpoints() {
155
155
  cfg.scope || "",
156
156
  cfg.authorizationEndpoint || "",
157
157
  cfg.tokenEndpoint || "",
158
+ cfg.endSessionEndpoint || "",
158
159
  cfg.discovery === false ? "0" : "1",
159
160
  ].join("|");
160
161
  if (endpointsInFlight && endpointsCacheKey === cacheKey)
@@ -164,8 +165,9 @@ async function resolveEndpoints() {
164
165
  const fallback = {
165
166
  authorizationEndpoint: cfg.authorizationEndpoint || rootEndpoint("/authorize"),
166
167
  tokenEndpoint: cfg.tokenEndpoint || rootEndpoint("/token"),
168
+ endSessionEndpoint: cfg.endSessionEndpoint || rootEndpoint("/api/logout"),
167
169
  };
168
- if (cfg.authorizationEndpoint && cfg.tokenEndpoint)
170
+ if (cfg.authorizationEndpoint && cfg.tokenEndpoint && cfg.endSessionEndpoint)
169
171
  return fallback;
170
172
  if (cfg.discovery === false || typeof fetch !== "function")
171
173
  return fallback;
@@ -184,6 +186,10 @@ async function resolveEndpoints() {
184
186
  (typeof metadata.token_endpoint === "string"
185
187
  ? metadata.token_endpoint
186
188
  : fallback.tokenEndpoint),
189
+ endSessionEndpoint: cfg.endSessionEndpoint ||
190
+ (typeof metadata.end_session_endpoint === "string"
191
+ ? metadata.end_session_endpoint
192
+ : fallback.endSessionEndpoint),
187
193
  };
188
194
  }
189
195
  catch {
@@ -744,7 +750,7 @@ export async function switchOrganization(organizationId, options = {}) {
744
750
  location.assign(switchUrl.toString());
745
751
  return null;
746
752
  }
747
- export function logout() {
753
+ function clearLocalSession() {
748
754
  memorySession = null;
749
755
  memoryRefreshToken = null;
750
756
  clearStoredIdToken();
@@ -755,6 +761,27 @@ export function logout() {
755
761
  sessionStorage.removeItem(OAUTH_STATE_KEY);
756
762
  localStorage.removeItem(REFRESH_TOKEN_KEY);
757
763
  }
764
+ export function logout() {
765
+ clearLocalSession();
766
+ }
767
+ export async function endSession(options = {}) {
768
+ const idTokenHint = options.idTokenHint ||
769
+ memorySession?.idToken ||
770
+ (tokenStorageMode() === "localStorage" ? getStoredIdToken() : null) ||
771
+ undefined;
772
+ const endpoints = await resolveEndpoints();
773
+ const url = new URL(endpoints.endSessionEndpoint);
774
+ if (idTokenHint)
775
+ url.searchParams.set("id_token_hint", idTokenHint);
776
+ if (options.postLogoutRedirectUri) {
777
+ url.searchParams.set("post_logout_redirect_uri", options.postLogoutRedirectUri);
778
+ url.searchParams.set("client_id", options.clientId || cfg.clientId);
779
+ }
780
+ if (options.state)
781
+ url.searchParams.set("state", options.state);
782
+ clearLocalSession();
783
+ location.assign(url.toString());
784
+ }
758
785
  export function getCurrentUser() {
759
786
  const idToken = memorySession?.idToken || (tokenStorageMode() === "localStorage" ? getStoredIdToken() : null);
760
787
  if (!idToken)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkauth/client",
3
- "version": "1.22.1",
3
+ "version": "1.23.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "directory": "packages/darkauth-client",
@@ -15,23 +15,22 @@
15
15
  "LICENSE",
16
16
  "README.md"
17
17
  ],
18
+ "dependencies": {
19
+ "jose": "^6.2.3"
20
+ },
21
+ "devDependencies": {
22
+ "@biomejs/biome": "^2.5.0",
23
+ "@types/node": "^25.9.3",
24
+ "typescript": "^6.0.3"
25
+ },
18
26
  "scripts": {
19
27
  "build": "tsc",
20
- "test": "npm run build && node --test --test-reporter=dot --experimental-specifier-resolution=node",
21
- "prepack": "npm run build",
28
+ "test": "tsc -p tsconfig.test.json && node --test --test-reporter=dot \".test-build/**/*.test.js\"",
22
29
  "typecheck": "tsc --noEmit",
23
- "prepare": "tsc",
24
30
  "format": "biome format --write .",
25
31
  "lint": "biome lint .",
26
32
  "check": "biome check .",
27
33
  "check:fix": "biome check --write .",
28
34
  "tidy": "biome check --write . && biome lint --write ."
29
- },
30
- "dependencies": {
31
- "jose": "^6.1.3"
32
- },
33
- "devDependencies": {
34
- "typescript": "^5.9.3",
35
- "@biomejs/biome": "^2.3.11"
36
35
  }
37
- }
36
+ }