@dloizides/auth-client 2.0.0 → 3.0.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/CHANGELOG.md +90 -0
- package/README.md +37 -1
- package/dist/{AuthClient-Dim7HPRz.d.ts → AuthClient-BGr8L03W.d.mts} +62 -35
- package/dist/{AuthClient-Dim7HPRz.d.mts → AuthClient-D95OMajD.d.ts} +62 -35
- package/dist/TokenResponse-CY1CaU2l.d.mts +59 -0
- package/dist/TokenResponse-CY1CaU2l.d.ts +59 -0
- package/dist/index.d.mts +109 -28
- package/dist/index.d.ts +109 -28
- package/dist/index.js +329 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +322 -20
- package/dist/index.mjs.map +1 -1
- package/dist/oidc/index.d.mts +127 -0
- package/dist/oidc/index.d.ts +127 -0
- package/dist/oidc/index.js +192 -0
- package/dist/oidc/index.js.map +1 -0
- package/dist/oidc/index.mjs +184 -0
- package/dist/oidc/index.mjs.map +1 -0
- package/dist/react.d.mts +2 -1
- package/dist/react.d.ts +2 -1
- package/package.json +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0 (2026-05-19)
|
|
4
|
+
|
|
5
|
+
Major release for Phase 2 of the identity-hardening initiative. Adds the
|
|
6
|
+
shared `BffAuthClient` — the same-origin client for a per-app
|
|
7
|
+
**Backend-For-Frontend** (`bff-katalogos`, `bff-erevna`). The BFF terminates
|
|
8
|
+
authentication server-side: the browser holds only an opaque httpOnly session
|
|
9
|
+
cookie, never a token.
|
|
10
|
+
|
|
11
|
+
This is the **new recommended auth surface**. The major bump signals that
|
|
12
|
+
recommendation — it is **not** a breaking change: every v2.x export
|
|
13
|
+
(`AuthClient`, the direct-KC ROPC adapters, `useDirectKcAuth`, the OIDC
|
|
14
|
+
primitives, storage adapters, hooks) remains and is unchanged. BaseClient
|
|
15
|
+
still consumes the direct-KC path; it is removed in a later phase.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `BffAuthClient` — same-origin client for a per-app BFF. Methods:
|
|
20
|
+
`login({username,password})` → `POST /bff/login`; `logout()` →
|
|
21
|
+
`POST /bff/logout`; `getCurrentUser()` → `GET /bff/me`; `register(...)`,
|
|
22
|
+
`forgotPassword(...)`, `resetPassword(...)` → the matching `/bff/*`
|
|
23
|
+
endpoints. Every call is a same-origin `fetch` with
|
|
24
|
+
`credentials: 'include'`; state-changing calls carry the `X-BFF-Csrf: 1`
|
|
25
|
+
header the `Bff.AspNetCore` anti-forgery middleware requires. Does **no
|
|
26
|
+
token handling** — the BFF owns tokens, the browser owns only the cookie.
|
|
27
|
+
- Types: `BffAuthClientOptions`, `BffLoginRequest`, `BffRegisterRequest`,
|
|
28
|
+
`BffForgotPasswordRequest`, `BffResetPasswordRequest`, `BffUser`.
|
|
29
|
+
|
|
30
|
+
## 2.1.0 (2026-05-17)
|
|
31
|
+
|
|
32
|
+
Additive release. Lays the groundwork for the "shrink identity service"
|
|
33
|
+
migration by extracting the OIDC primitives that three apps (BaseClient,
|
|
34
|
+
`apps/erevna-web`, `apps/katalogos-web`) had each duplicated in their own
|
|
35
|
+
`useKeycloakExchange.ts` files. No breaking changes — v2.0 callers continue
|
|
36
|
+
to work unchanged.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
#### New `/oidc` sub-path entry
|
|
41
|
+
|
|
42
|
+
- `@dloizides/auth-client/oidc` — Pure OIDC primitives (no React, no hooks).
|
|
43
|
+
Lets non-React consumers tree-shake the `AuthClient` class away entirely.
|
|
44
|
+
|
|
45
|
+
#### OIDC primitives (also re-exported from the main entry)
|
|
46
|
+
|
|
47
|
+
- `fetchDiscoveryDocument({ issuerUrl, http })` — Fetches
|
|
48
|
+
`{issuer}/.well-known/openid-configuration` and caches the result per
|
|
49
|
+
issuer URL for the lifetime of the process. Throws on non-2xx or invalid
|
|
50
|
+
metadata. Cache cleared with `clearDiscoveryCache()` (test-only).
|
|
51
|
+
- `generateCodeVerifier(length?)` — RFC 7636-compliant PKCE verifier
|
|
52
|
+
generator. Defaults to 64 chars; enforces the 43..128 band.
|
|
53
|
+
- `deriveCodeChallenge(verifier)` — `BASE64URL(SHA256(verifier))` via
|
|
54
|
+
`crypto.subtle` (browser + Node 16+). Matches the RFC 7636 Appendix B
|
|
55
|
+
test vector.
|
|
56
|
+
- `generatePkcePair(length?)` — Convenience: fresh verifier + matching S256
|
|
57
|
+
challenge in one call.
|
|
58
|
+
- `exchangeAuthorizationCode({ http, baseUrl, realm, clientId, code,
|
|
59
|
+
redirectUri, codeVerifier })` — POSTs `grant_type=authorization_code` to
|
|
60
|
+
the realm's token endpoint. Returns a normalised `TokenResponse`.
|
|
61
|
+
- `refreshAccessToken({ http, baseUrl, realm, clientId, refreshToken })` —
|
|
62
|
+
POSTs `grant_type=refresh_token`. Same return shape.
|
|
63
|
+
|
|
64
|
+
#### `AuthClient` v2.1 surface
|
|
65
|
+
|
|
66
|
+
- `useDirectKcAuth?: boolean` config flag. Default `false`. When `true`,
|
|
67
|
+
apps can route their PKCE flow through the shared `exchangeAuthorizationCode`
|
|
68
|
+
primitive instead of the proxied identity-api `/auth/login` flow. The
|
|
69
|
+
flag is read-only at runtime via `AuthClient.isDirectMode()` so apps can
|
|
70
|
+
render conditionally on whether they've opted in.
|
|
71
|
+
- `acceptDirectKcTokens(response)` — Persists a `TokenResponse` produced by
|
|
72
|
+
the direct-KC flow into the configured storage, marks the inactivity
|
|
73
|
+
tracker active, and fires `onTokenAcquired`. Use this from the app's
|
|
74
|
+
`useKeycloakExchange.ts` hook after `exchangeAuthorizationCode()` resolves.
|
|
75
|
+
- `acceptDirectKcRefresh(response)` — Same as above but fires
|
|
76
|
+
`onTokenRefreshed` instead. Use after `refreshAccessToken()` swaps.
|
|
77
|
+
- `onTokenAcquired?: (tokens) => void` collaborator — fires after any login
|
|
78
|
+
path (OTP, password, direct-KC) successfully persists a fresh token
|
|
79
|
+
bundle. For app-side analytics/logging only — NOT designed for BFF
|
|
80
|
+
integration (Phase 2 designs that fresh).
|
|
81
|
+
- `onTokenRefreshed?: (tokens) => void` collaborator — fires after any
|
|
82
|
+
refresh (interceptor or direct-KC) persists a fresh bundle.
|
|
83
|
+
|
|
84
|
+
### Notes
|
|
85
|
+
|
|
86
|
+
- The `useDirectKcAuth` flag is the dormant-path flip mechanism. After v2.1
|
|
87
|
+
ships, apps still default to the proxied path. Per-app cutover (flipping
|
|
88
|
+
the flag to `true`) happens in subsequent steps of the
|
|
89
|
+
shrink-identity-to-tenant-service migration.
|
|
90
|
+
- The proxied `/auth/login`, `/auth/refresh`, `/auth/refresh-cookie` methods
|
|
91
|
+
on `AuthApiClient` are unchanged — production apps still call them.
|
|
92
|
+
|
|
3
93
|
## 2.0.0 (2026-05-07)
|
|
4
94
|
|
|
5
95
|
Major release. Extends the realm-aware OIDC core into a single source of truth for every auth surface in the dloizides.com portfolio: web cookies, mobile secure storage, biometric gating, silent token refresh with single-flight, inactivity enforcement, password reset, and sessions management.
|
package/README.md
CHANGED
|
@@ -119,6 +119,41 @@ const storage = new SecureStoreTokenStorage({
|
|
|
119
119
|
await biometricGate.hydrate();
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
## BFF auth (v3 — recommended)
|
|
123
|
+
|
|
124
|
+
`BffAuthClient` is the same-origin client for a per-app **Backend-For-Frontend**
|
|
125
|
+
(`bff-katalogos`, `bff-erevna`). The BFF terminates authentication
|
|
126
|
+
server-side: it does ROPC against Keycloak with a confidential client, stores
|
|
127
|
+
the tokens in a Redis vault, and hands the browser only an opaque httpOnly
|
|
128
|
+
session cookie. The SPA never sees a token — an XSS cannot exfiltrate one.
|
|
129
|
+
|
|
130
|
+
`BffAuthClient` does **no token handling**: every call is a same-origin
|
|
131
|
+
`fetch` with `credentials: 'include'`, and state-changing calls carry the
|
|
132
|
+
`X-BFF-Csrf: 1` header the BFF anti-forgery middleware requires.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { BffAuthClient, createFetchHttpClient } from '@dloizides/auth-client';
|
|
136
|
+
|
|
137
|
+
const bff = new BffAuthClient({
|
|
138
|
+
http: createFetchHttpClient(window.fetch.bind(window)),
|
|
139
|
+
// baseUrl defaults to '' (same-origin) — the production wiring.
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Login — the BFF does ROPC server-side and sets the session cookie.
|
|
143
|
+
const user = await bff.login({ username, password });
|
|
144
|
+
|
|
145
|
+
// Bootstrap on app load — null when there is no live session.
|
|
146
|
+
const current = await bff.getCurrentUser();
|
|
147
|
+
|
|
148
|
+
await bff.register({ firstName, lastName, username, email, password, tenantName });
|
|
149
|
+
await bff.forgotPassword({ email, resetUrlTemplate });
|
|
150
|
+
await bff.resetPassword({ token, newPassword });
|
|
151
|
+
await bff.logout();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The direct-KC `AuthClient` / ROPC surface below is retained for consumers not
|
|
155
|
+
yet on a BFF; it is deprecated and removed once every app has migrated.
|
|
156
|
+
|
|
122
157
|
## React Query hooks
|
|
123
158
|
|
|
124
159
|
```ts
|
|
@@ -156,7 +191,8 @@ auth.on('sessionExpired', () => {
|
|
|
156
191
|
|
|
157
192
|
### Core (`@dloizides/auth-client`)
|
|
158
193
|
|
|
159
|
-
- `
|
|
194
|
+
- `BffAuthClient` — same-origin client for a per-app Backend-For-Frontend. `login()`, `logout()`, `getCurrentUser()`, `register()`, `forgotPassword()`, `resetPassword()`. No token handling — the BFF owns tokens, the browser owns only an httpOnly cookie. **The recommended auth surface (v3).**
|
|
195
|
+
- `AuthClient` — realm-aware orchestrator. `init()`, `refresh()`, `loginWithOtp()`, `loginWithPassword()`, `logout({ everywhere })`, `requestPasswordReset()`, `confirmPasswordReset()`, plus the v1 surface (`getAccessToken`, `getTokens`, `setTokens`, `clearTokens`, `buildAuthorizationUrl`, etc.). Direct-KC ROPC; deprecated in favour of `BffAuthClient`.
|
|
160
196
|
- `AuthApiClient` — typed wrapper for IdentityService auth endpoints.
|
|
161
197
|
- `AuthEventEmitter` — `sessionExpired` event.
|
|
162
198
|
- `RefreshInterceptor` — single-flight refresh queue.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { H as HttpClient, T as TokenResponse } from './TokenResponse-CY1CaU2l.mjs';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Tiny dependency-free event emitter for auth lifecycle events.
|
|
3
5
|
*
|
|
@@ -19,39 +21,6 @@ declare class AuthEventEmitter {
|
|
|
19
21
|
clear(): void;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
/**
|
|
23
|
-
* Minimal HTTP transport the package depends on.
|
|
24
|
-
*
|
|
25
|
-
* `AuthClient` orchestrates token-related HTTP calls (login, refresh, logout,
|
|
26
|
-
* password reset) but doesn't import `fetch` directly — keeping the package
|
|
27
|
-
* runtime-agnostic. Consumers wire native fetch, axios, ky, or whatever their
|
|
28
|
-
* platform exposes.
|
|
29
|
-
*/
|
|
30
|
-
interface HttpRequest {
|
|
31
|
-
url: string;
|
|
32
|
-
method: 'GET' | 'POST' | 'DELETE';
|
|
33
|
-
headers?: Record<string, string>;
|
|
34
|
-
/** When set, body is sent as the request body; serialization is the caller's job. */
|
|
35
|
-
body?: string;
|
|
36
|
-
/** Browser fetch only — pass through `credentials: 'include'` for cookie auth. */
|
|
37
|
-
credentials?: 'include' | 'same-origin' | 'omit';
|
|
38
|
-
}
|
|
39
|
-
interface HttpResponse {
|
|
40
|
-
status: number;
|
|
41
|
-
ok: boolean;
|
|
42
|
-
/** Parsed body (already JSON-decoded). `undefined` for 204 / empty bodies. */
|
|
43
|
-
data?: unknown;
|
|
44
|
-
}
|
|
45
|
-
type HttpClient = (request: HttpRequest) => Promise<HttpResponse>;
|
|
46
|
-
/**
|
|
47
|
-
* Wrap the platform's native `fetch` into the package's `HttpClient` shape.
|
|
48
|
-
* Decoded JSON when `Content-Type` is JSON, otherwise leaves data undefined.
|
|
49
|
-
*
|
|
50
|
-
* Errors thrown by `fetch` (network / abort) are NOT swallowed — callers
|
|
51
|
-
* decide whether to treat them as session-ending.
|
|
52
|
-
*/
|
|
53
|
-
declare function createFetchHttpClient(fetchImpl: typeof fetch): HttpClient;
|
|
54
|
-
|
|
55
24
|
/**
|
|
56
25
|
* Backend session record returned by `GET /me/sessions`.
|
|
57
26
|
*
|
|
@@ -328,6 +297,34 @@ interface AuthClientCollaborators {
|
|
|
328
297
|
interceptor?: RefreshInterceptor;
|
|
329
298
|
inactivityTracker?: InactivityTracker;
|
|
330
299
|
events?: AuthEventEmitter;
|
|
300
|
+
/**
|
|
301
|
+
* Observability hook fired when a fresh token bundle has been acquired
|
|
302
|
+
* (any login path: OTP, password, or direct-KC PKCE). For app-side
|
|
303
|
+
* analytics/logging only — NOT for BFF integration (Phase 2 designs that
|
|
304
|
+
* fresh).
|
|
305
|
+
*/
|
|
306
|
+
onTokenAcquired?: (tokens: AuthTokens) => void;
|
|
307
|
+
/**
|
|
308
|
+
* Observability hook fired when an existing token bundle has been
|
|
309
|
+
* refreshed. For app-side analytics/logging only.
|
|
310
|
+
*/
|
|
311
|
+
onTokenRefreshed?: (tokens: AuthTokens) => void;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Direct-to-KC (PKCE) routing flag added in v2.1.0.
|
|
315
|
+
*
|
|
316
|
+
* When `true`, `AuthClient` consumers can route their PKCE auth code through
|
|
317
|
+
* the shared OIDC primitives (`exchangeAuthorizationCodeViaOidc`,
|
|
318
|
+
* `refreshTokensViaOidc`) instead of the proxied identity-api `/auth/login`
|
|
319
|
+
* + `/auth/refresh` flow.
|
|
320
|
+
*
|
|
321
|
+
* Default `false` — v2.0 behavior unchanged.
|
|
322
|
+
*
|
|
323
|
+
* The flag is read-only at runtime (`isDirectMode()`) so apps can render
|
|
324
|
+
* conditionally on whether they've opted in.
|
|
325
|
+
*/
|
|
326
|
+
interface DirectKcOptions {
|
|
327
|
+
useDirectKcAuth?: boolean;
|
|
331
328
|
}
|
|
332
329
|
interface LoginOptions {
|
|
333
330
|
/** When true, request `offline_access` scope so the IdP issues a long-lived refresh token. */
|
|
@@ -339,15 +336,45 @@ interface LogoutOptions {
|
|
|
339
336
|
}
|
|
340
337
|
declare class AuthClient {
|
|
341
338
|
private readonly config;
|
|
339
|
+
private readonly directKcAuth;
|
|
342
340
|
private readonly tokenStorage;
|
|
343
341
|
private readonly api;
|
|
344
342
|
private readonly interceptor;
|
|
345
343
|
private readonly inactivityTracker;
|
|
346
344
|
private readonly events;
|
|
345
|
+
private readonly onTokenAcquired;
|
|
346
|
+
private readonly onTokenRefreshed;
|
|
347
347
|
/**
|
|
348
348
|
* @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.
|
|
349
349
|
*/
|
|
350
|
-
constructor(config: AuthClientConfig, storage: TokenStorage, collaborators?: AuthClientCollaborators);
|
|
350
|
+
constructor(config: AuthClientConfig & DirectKcOptions, storage: TokenStorage, collaborators?: AuthClientCollaborators);
|
|
351
|
+
/**
|
|
352
|
+
* Whether this client is configured to route auth flows directly to
|
|
353
|
+
* Keycloak (v2.1.0 direct-KC path) instead of through the proxied
|
|
354
|
+
* identity-api `/auth/*` endpoints.
|
|
355
|
+
*
|
|
356
|
+
* Apps can render conditionally on this — e.g. to swap a login form for
|
|
357
|
+
* a "Sign in with Keycloak" redirect button.
|
|
358
|
+
*/
|
|
359
|
+
isDirectMode(): boolean;
|
|
360
|
+
/**
|
|
361
|
+
* Persist a token bundle produced by an external flow (e.g. the
|
|
362
|
+
* app-side `useKeycloakExchange` hook that consumes the shared
|
|
363
|
+
* `exchangeAuthorizationCode` primitive). Fires `onTokenAcquired` after
|
|
364
|
+
* persistence and marks the inactivity tracker active.
|
|
365
|
+
*
|
|
366
|
+
* Designed for the v2.1.0 direct-KC path where the PKCE code exchange
|
|
367
|
+
* happens in the app's React-Query hook (which needs `useDispatch`/etc.)
|
|
368
|
+
* but the token persistence + observability should still flow through
|
|
369
|
+
* the shared client.
|
|
370
|
+
*/
|
|
371
|
+
acceptDirectKcTokens(response: TokenResponse): Promise<AuthTokens>;
|
|
372
|
+
/**
|
|
373
|
+
* Same as {@link acceptDirectKcTokens} but fires `onTokenRefreshed`.
|
|
374
|
+
* Use after a `refreshAccessToken()` swap to keep observability counts
|
|
375
|
+
* separated between "fresh login" and "silent refresh".
|
|
376
|
+
*/
|
|
377
|
+
acceptDirectKcRefresh(response: TokenResponse): Promise<AuthTokens>;
|
|
351
378
|
/**
|
|
352
379
|
* Build an {@link AuthClient} from a standalone issuer URL by parsing the
|
|
353
380
|
* realm and base URL. Useful when migrating from the legacy
|
|
@@ -430,4 +457,4 @@ declare class AuthClient {
|
|
|
430
457
|
private resolveScope;
|
|
431
458
|
}
|
|
432
459
|
|
|
433
|
-
export {
|
|
460
|
+
export { AuthApiClient as A, type DirectKcOptions as D, type ForgotPasswordRequest as F, type InactivityStore as I, type LoginOptions as L, type OtpLoginRequest as O, type PasswordLoginRequest as P, type ResetPasswordRequest as R, type TokenStorage as T, type AuthSessionInfo as a, AuthClient as b, type AuthTokens as c, type AuthApiClientOptions as d, type AuthClientCollaborators as e, type AuthClientConfig as f, type AuthClientFromIssuerInput as g, AuthEventEmitter as h, type AuthEventListener as i, type AuthEventName as j, type AuthEventUnsubscribe as k, InactivityTracker as l, type InactivityTrackerOptions as m, type LogoutOptions as n, type RawAuthLoginResponse as o, type RefreshFn as p, RefreshInterceptor as q, type RefreshInterceptorOptions as r };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { H as HttpClient, T as TokenResponse } from './TokenResponse-CY1CaU2l.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Tiny dependency-free event emitter for auth lifecycle events.
|
|
3
5
|
*
|
|
@@ -19,39 +21,6 @@ declare class AuthEventEmitter {
|
|
|
19
21
|
clear(): void;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
/**
|
|
23
|
-
* Minimal HTTP transport the package depends on.
|
|
24
|
-
*
|
|
25
|
-
* `AuthClient` orchestrates token-related HTTP calls (login, refresh, logout,
|
|
26
|
-
* password reset) but doesn't import `fetch` directly — keeping the package
|
|
27
|
-
* runtime-agnostic. Consumers wire native fetch, axios, ky, or whatever their
|
|
28
|
-
* platform exposes.
|
|
29
|
-
*/
|
|
30
|
-
interface HttpRequest {
|
|
31
|
-
url: string;
|
|
32
|
-
method: 'GET' | 'POST' | 'DELETE';
|
|
33
|
-
headers?: Record<string, string>;
|
|
34
|
-
/** When set, body is sent as the request body; serialization is the caller's job. */
|
|
35
|
-
body?: string;
|
|
36
|
-
/** Browser fetch only — pass through `credentials: 'include'` for cookie auth. */
|
|
37
|
-
credentials?: 'include' | 'same-origin' | 'omit';
|
|
38
|
-
}
|
|
39
|
-
interface HttpResponse {
|
|
40
|
-
status: number;
|
|
41
|
-
ok: boolean;
|
|
42
|
-
/** Parsed body (already JSON-decoded). `undefined` for 204 / empty bodies. */
|
|
43
|
-
data?: unknown;
|
|
44
|
-
}
|
|
45
|
-
type HttpClient = (request: HttpRequest) => Promise<HttpResponse>;
|
|
46
|
-
/**
|
|
47
|
-
* Wrap the platform's native `fetch` into the package's `HttpClient` shape.
|
|
48
|
-
* Decoded JSON when `Content-Type` is JSON, otherwise leaves data undefined.
|
|
49
|
-
*
|
|
50
|
-
* Errors thrown by `fetch` (network / abort) are NOT swallowed — callers
|
|
51
|
-
* decide whether to treat them as session-ending.
|
|
52
|
-
*/
|
|
53
|
-
declare function createFetchHttpClient(fetchImpl: typeof fetch): HttpClient;
|
|
54
|
-
|
|
55
24
|
/**
|
|
56
25
|
* Backend session record returned by `GET /me/sessions`.
|
|
57
26
|
*
|
|
@@ -328,6 +297,34 @@ interface AuthClientCollaborators {
|
|
|
328
297
|
interceptor?: RefreshInterceptor;
|
|
329
298
|
inactivityTracker?: InactivityTracker;
|
|
330
299
|
events?: AuthEventEmitter;
|
|
300
|
+
/**
|
|
301
|
+
* Observability hook fired when a fresh token bundle has been acquired
|
|
302
|
+
* (any login path: OTP, password, or direct-KC PKCE). For app-side
|
|
303
|
+
* analytics/logging only — NOT for BFF integration (Phase 2 designs that
|
|
304
|
+
* fresh).
|
|
305
|
+
*/
|
|
306
|
+
onTokenAcquired?: (tokens: AuthTokens) => void;
|
|
307
|
+
/**
|
|
308
|
+
* Observability hook fired when an existing token bundle has been
|
|
309
|
+
* refreshed. For app-side analytics/logging only.
|
|
310
|
+
*/
|
|
311
|
+
onTokenRefreshed?: (tokens: AuthTokens) => void;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Direct-to-KC (PKCE) routing flag added in v2.1.0.
|
|
315
|
+
*
|
|
316
|
+
* When `true`, `AuthClient` consumers can route their PKCE auth code through
|
|
317
|
+
* the shared OIDC primitives (`exchangeAuthorizationCodeViaOidc`,
|
|
318
|
+
* `refreshTokensViaOidc`) instead of the proxied identity-api `/auth/login`
|
|
319
|
+
* + `/auth/refresh` flow.
|
|
320
|
+
*
|
|
321
|
+
* Default `false` — v2.0 behavior unchanged.
|
|
322
|
+
*
|
|
323
|
+
* The flag is read-only at runtime (`isDirectMode()`) so apps can render
|
|
324
|
+
* conditionally on whether they've opted in.
|
|
325
|
+
*/
|
|
326
|
+
interface DirectKcOptions {
|
|
327
|
+
useDirectKcAuth?: boolean;
|
|
331
328
|
}
|
|
332
329
|
interface LoginOptions {
|
|
333
330
|
/** When true, request `offline_access` scope so the IdP issues a long-lived refresh token. */
|
|
@@ -339,15 +336,45 @@ interface LogoutOptions {
|
|
|
339
336
|
}
|
|
340
337
|
declare class AuthClient {
|
|
341
338
|
private readonly config;
|
|
339
|
+
private readonly directKcAuth;
|
|
342
340
|
private readonly tokenStorage;
|
|
343
341
|
private readonly api;
|
|
344
342
|
private readonly interceptor;
|
|
345
343
|
private readonly inactivityTracker;
|
|
346
344
|
private readonly events;
|
|
345
|
+
private readonly onTokenAcquired;
|
|
346
|
+
private readonly onTokenRefreshed;
|
|
347
347
|
/**
|
|
348
348
|
* @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.
|
|
349
349
|
*/
|
|
350
|
-
constructor(config: AuthClientConfig, storage: TokenStorage, collaborators?: AuthClientCollaborators);
|
|
350
|
+
constructor(config: AuthClientConfig & DirectKcOptions, storage: TokenStorage, collaborators?: AuthClientCollaborators);
|
|
351
|
+
/**
|
|
352
|
+
* Whether this client is configured to route auth flows directly to
|
|
353
|
+
* Keycloak (v2.1.0 direct-KC path) instead of through the proxied
|
|
354
|
+
* identity-api `/auth/*` endpoints.
|
|
355
|
+
*
|
|
356
|
+
* Apps can render conditionally on this — e.g. to swap a login form for
|
|
357
|
+
* a "Sign in with Keycloak" redirect button.
|
|
358
|
+
*/
|
|
359
|
+
isDirectMode(): boolean;
|
|
360
|
+
/**
|
|
361
|
+
* Persist a token bundle produced by an external flow (e.g. the
|
|
362
|
+
* app-side `useKeycloakExchange` hook that consumes the shared
|
|
363
|
+
* `exchangeAuthorizationCode` primitive). Fires `onTokenAcquired` after
|
|
364
|
+
* persistence and marks the inactivity tracker active.
|
|
365
|
+
*
|
|
366
|
+
* Designed for the v2.1.0 direct-KC path where the PKCE code exchange
|
|
367
|
+
* happens in the app's React-Query hook (which needs `useDispatch`/etc.)
|
|
368
|
+
* but the token persistence + observability should still flow through
|
|
369
|
+
* the shared client.
|
|
370
|
+
*/
|
|
371
|
+
acceptDirectKcTokens(response: TokenResponse): Promise<AuthTokens>;
|
|
372
|
+
/**
|
|
373
|
+
* Same as {@link acceptDirectKcTokens} but fires `onTokenRefreshed`.
|
|
374
|
+
* Use after a `refreshAccessToken()` swap to keep observability counts
|
|
375
|
+
* separated between "fresh login" and "silent refresh".
|
|
376
|
+
*/
|
|
377
|
+
acceptDirectKcRefresh(response: TokenResponse): Promise<AuthTokens>;
|
|
351
378
|
/**
|
|
352
379
|
* Build an {@link AuthClient} from a standalone issuer URL by parsing the
|
|
353
380
|
* realm and base URL. Useful when migrating from the legacy
|
|
@@ -430,4 +457,4 @@ declare class AuthClient {
|
|
|
430
457
|
private resolveScope;
|
|
431
458
|
}
|
|
432
459
|
|
|
433
|
-
export {
|
|
460
|
+
export { AuthApiClient as A, type DirectKcOptions as D, type ForgotPasswordRequest as F, type InactivityStore as I, type LoginOptions as L, type OtpLoginRequest as O, type PasswordLoginRequest as P, type ResetPasswordRequest as R, type TokenStorage as T, type AuthSessionInfo as a, AuthClient as b, type AuthTokens as c, type AuthApiClientOptions as d, type AuthClientCollaborators as e, type AuthClientConfig as f, type AuthClientFromIssuerInput as g, AuthEventEmitter as h, type AuthEventListener as i, type AuthEventName as j, type AuthEventUnsubscribe as k, InactivityTracker as l, type InactivityTrackerOptions as m, type LogoutOptions as n, type RawAuthLoginResponse as o, type RefreshFn as p, RefreshInterceptor as q, type RefreshInterceptorOptions as r };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP transport the package depends on.
|
|
3
|
+
*
|
|
4
|
+
* `AuthClient` orchestrates token-related HTTP calls (login, refresh, logout,
|
|
5
|
+
* password reset) but doesn't import `fetch` directly — keeping the package
|
|
6
|
+
* runtime-agnostic. Consumers wire native fetch, axios, ky, or whatever their
|
|
7
|
+
* platform exposes.
|
|
8
|
+
*/
|
|
9
|
+
interface HttpRequest {
|
|
10
|
+
url: string;
|
|
11
|
+
method: 'GET' | 'POST' | 'DELETE';
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
/** When set, body is sent as the request body; serialization is the caller's job. */
|
|
14
|
+
body?: string;
|
|
15
|
+
/** Browser fetch only — pass through `credentials: 'include'` for cookie auth. */
|
|
16
|
+
credentials?: 'include' | 'same-origin' | 'omit';
|
|
17
|
+
}
|
|
18
|
+
interface HttpResponse {
|
|
19
|
+
status: number;
|
|
20
|
+
ok: boolean;
|
|
21
|
+
/** Parsed body (already JSON-decoded). `undefined` for 204 / empty bodies. */
|
|
22
|
+
data?: unknown;
|
|
23
|
+
}
|
|
24
|
+
type HttpClient = (request: HttpRequest) => Promise<HttpResponse>;
|
|
25
|
+
/**
|
|
26
|
+
* Wrap the platform's native `fetch` into the package's `HttpClient` shape.
|
|
27
|
+
* Decoded JSON when `Content-Type` is JSON, otherwise leaves data undefined.
|
|
28
|
+
*
|
|
29
|
+
* Errors thrown by `fetch` (network / abort) are NOT swallowed — callers
|
|
30
|
+
* decide whether to treat them as session-ending.
|
|
31
|
+
*/
|
|
32
|
+
declare function createFetchHttpClient(fetchImpl: typeof fetch): HttpClient;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Raw token endpoint response (snake_case, OIDC standard).
|
|
36
|
+
*/
|
|
37
|
+
interface RawTokenResponse {
|
|
38
|
+
access_token: string;
|
|
39
|
+
refresh_token?: string;
|
|
40
|
+
id_token?: string;
|
|
41
|
+
expires_in?: number;
|
|
42
|
+
token_type?: string;
|
|
43
|
+
scope?: string;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Application-friendly camelCase view of a token endpoint response.
|
|
48
|
+
*/
|
|
49
|
+
interface TokenResponse {
|
|
50
|
+
accessToken: string;
|
|
51
|
+
refreshToken?: string;
|
|
52
|
+
idToken?: string;
|
|
53
|
+
/** Seconds until expiry, as returned by Keycloak. */
|
|
54
|
+
expiresIn?: number;
|
|
55
|
+
tokenType?: string;
|
|
56
|
+
scope?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { type HttpClient as H, type RawTokenResponse as R, type TokenResponse as T, type HttpRequest as a, type HttpResponse as b, createFetchHttpClient as c };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP transport the package depends on.
|
|
3
|
+
*
|
|
4
|
+
* `AuthClient` orchestrates token-related HTTP calls (login, refresh, logout,
|
|
5
|
+
* password reset) but doesn't import `fetch` directly — keeping the package
|
|
6
|
+
* runtime-agnostic. Consumers wire native fetch, axios, ky, or whatever their
|
|
7
|
+
* platform exposes.
|
|
8
|
+
*/
|
|
9
|
+
interface HttpRequest {
|
|
10
|
+
url: string;
|
|
11
|
+
method: 'GET' | 'POST' | 'DELETE';
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
/** When set, body is sent as the request body; serialization is the caller's job. */
|
|
14
|
+
body?: string;
|
|
15
|
+
/** Browser fetch only — pass through `credentials: 'include'` for cookie auth. */
|
|
16
|
+
credentials?: 'include' | 'same-origin' | 'omit';
|
|
17
|
+
}
|
|
18
|
+
interface HttpResponse {
|
|
19
|
+
status: number;
|
|
20
|
+
ok: boolean;
|
|
21
|
+
/** Parsed body (already JSON-decoded). `undefined` for 204 / empty bodies. */
|
|
22
|
+
data?: unknown;
|
|
23
|
+
}
|
|
24
|
+
type HttpClient = (request: HttpRequest) => Promise<HttpResponse>;
|
|
25
|
+
/**
|
|
26
|
+
* Wrap the platform's native `fetch` into the package's `HttpClient` shape.
|
|
27
|
+
* Decoded JSON when `Content-Type` is JSON, otherwise leaves data undefined.
|
|
28
|
+
*
|
|
29
|
+
* Errors thrown by `fetch` (network / abort) are NOT swallowed — callers
|
|
30
|
+
* decide whether to treat them as session-ending.
|
|
31
|
+
*/
|
|
32
|
+
declare function createFetchHttpClient(fetchImpl: typeof fetch): HttpClient;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Raw token endpoint response (snake_case, OIDC standard).
|
|
36
|
+
*/
|
|
37
|
+
interface RawTokenResponse {
|
|
38
|
+
access_token: string;
|
|
39
|
+
refresh_token?: string;
|
|
40
|
+
id_token?: string;
|
|
41
|
+
expires_in?: number;
|
|
42
|
+
token_type?: string;
|
|
43
|
+
scope?: string;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Application-friendly camelCase view of a token endpoint response.
|
|
48
|
+
*/
|
|
49
|
+
interface TokenResponse {
|
|
50
|
+
accessToken: string;
|
|
51
|
+
refreshToken?: string;
|
|
52
|
+
idToken?: string;
|
|
53
|
+
/** Seconds until expiry, as returned by Keycloak. */
|
|
54
|
+
expiresIn?: number;
|
|
55
|
+
tokenType?: string;
|
|
56
|
+
scope?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { type HttpClient as H, type RawTokenResponse as R, type TokenResponse as T, type HttpRequest as a, type HttpResponse as b, createFetchHttpClient as c };
|