@hearth-auth/sdk 3.0.1 → 3.1.1

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
@@ -35,23 +35,30 @@ const client = new HearthClient({
35
35
  clientId: "<client-id>",
36
36
  clientSecret: "<client-secret>", // confidential clients only
37
37
  realmId: "<your-realm-id>",
38
+ audience: "https://api.example.com", // expected `aud`; default "hearth"
38
39
  });
39
40
 
40
41
  const claims = await client.verifyToken(accessToken); // throws on a bad token
41
42
  claims.subject();
42
43
  claims.hasPermission("docs.write");
43
44
 
44
- // RBAC facade — local, synchronous permission checks from the JWT
45
+ // RBAC facade — verifies the token, then checks its claims
45
46
  const hearth = createHearth({
46
47
  baseUrl: "https://hearth.example.com",
47
48
  realmId: "<your-realm-id>",
48
- getToken: () => localStorage.getItem("access_token"),
49
+ issuerUrl: "https://hearth.example.com/realms/<realm-slug>",
50
+ getToken: () => currentAccessToken,
49
51
  });
52
+ await hearth.hasPermission("docs.write");
50
53
  ```
51
54
 
52
55
  `HearthClient` reads every endpoint URL from `{issuerUrl}/.well-known/openid-configuration` on first use and caches it. `httpTimeout` (default 10 000 ms) applies to every request it makes. Call `client.invalidateCache()` to drop the cached discovery document, JWKS and introspection client.
53
56
 
54
- `createHearth` gives you a zero-network RBAC facade that reads claims from the JWT in memory.
57
+ `createHearth` gives you an RBAC facade over the token `getToken()` returns. Each check verifies that token against the realm JWKS (fetched once, then cached) before it reads a claim.
58
+
59
+ ### Audience
60
+
61
+ `verifyToken` always checks `aud` (RFC 9068 §4); the check cannot be turned off. The expected audience is the `audience` option, default `"hearth"` (exported as `DEFAULT_AUDIENCE`): the audience Hearth mints when a client names no resource. An API registered as a protected resource sets its resource URI. The client ID is not the audience: only ID tokens are checked against a client ID (OIDC Core). An empty `audience` throws `ConfigurationError`; a token whose `aud` does not contain the expected audience throws `TokenAudienceError`. `JwksClient` takes the same option, and `VerifyOptions.audience` overrides it for one `verify()` call.
55
62
 
56
63
  ---
57
64
 
@@ -106,69 +113,93 @@ Every token-endpoint failure throws `OAuthFlowError` with `statusCode` and the O
106
113
 
107
114
  A single-page app has no server session to hold the verifier. Use `createHearthAuth`, or build the flow from `generateCodeVerifier`, `generateCodeChallenge` and `buildAuthorizationUrl`.
108
115
 
116
+ ```typescript
117
+ import { createHearthAuth, HearthApiClient } from "@hearth-auth/sdk";
118
+
119
+ const auth = createHearthAuth(
120
+ new HearthApiClient({ baseUrl: "https://hearth.example.com", realmId: "<realm-id>" }),
121
+ {
122
+ clientId: "<client-id>",
123
+ redirectUri: "https://app.example.com/callback",
124
+ hearthUrl: "https://hearth.example.com",
125
+ realmSlug: "<realm-slug>",
126
+ // storage: "sessionStorage" (default) | "localStorage" | "memory" | { getItem, setItem, removeItem }
127
+ // storageKeyPrefix: "hearth_" (default)
128
+ },
129
+ );
130
+
131
+ await auth.startLogin();
132
+ // on the callback page: await auth.handleCallback(code, state);
133
+ auth.getAccessToken(); // also getRefreshToken(), getIdToken(), isAuthenticated(), clearTokens()
134
+ ```
135
+
136
+ The access token stays in memory. The refresh token, ID token and the PKCE verifier and `state` of a login in flight go to the configured `storage`, `sessionStorage` by default (scoped to one tab, cleared when it closes). `"memory"` does not survive the login redirect. At creation the facade removes `hearth_refresh_token` and `hearth_id_token` from `localStorage` unless `localStorage` is the configured store.
137
+
109
138
  ---
110
139
 
111
140
  ## RBAC capabilities
112
141
 
113
- All synchronous helpers decode the JWT returned by `getToken()` **locally** — no network call, no cache, no lock. When the token is absent or malformed, every predicate returns `false`.
142
+ Every predicate verifies the token returned by `getToken()` before it reads a claim: the EdDSA signature against the realm JWKS (fetched from `issuerUrl` once, then cached), plus `exp`, `nbf`, `iat`, `iss` (must equal `issuerUrl`) and `aud` (must contain `audience`, default `"hearth"`). When the token is absent or does not verify, every predicate resolves `false`. With `sessionVersions` enabled, a predicate may reject with `SessionVersionRevokedError` or `SessionVersionCacheStaleError`.
114
143
 
115
144
  ```typescript
116
145
  const hearth = createHearth({
117
146
  baseUrl: "https://hearth.example.com",
118
147
  realmId: "<your-realm-id>",
119
- getToken: () => sessionStorage.getItem("access_token"),
148
+ issuerUrl: "https://hearth.example.com/realms/<realm-slug>", // required: the realm issuer
149
+ audience: "hearth", // optional; this is the default
150
+ getToken: () => auth.getAccessToken(), // may also return a Promise
120
151
  });
121
152
  ```
122
153
 
123
- ### `hasPermission(permission: string): boolean`
154
+ ### `hasPermission(permission: string): Promise<boolean>`
124
155
 
125
- Returns `true` iff the JWT `permissions` claim contains `permission`. Use this for feature gates and API guards.
156
+ Resolves `true` iff the token verifies and its `permissions` claim contains `permission`. Use this for feature gates and API guards.
126
157
 
127
158
  ```typescript
128
- if (hearth.hasPermission("docs.versions.read")) {
159
+ if (await hearth.hasPermission("docs.versions.read")) {
129
160
  renderVersionHistory();
130
161
  }
131
162
  ```
132
163
 
133
- ### `hasRole(role: string): boolean`
164
+ ### `hasRole(role: string): Promise<boolean>`
134
165
 
135
- Returns `true` iff the JWT `roles` claim contains `role`. Useful for UI personalization and coarse-grained access.
166
+ Resolves `true` iff the token verifies and its `roles` claim contains `role`. Useful for UI personalization and coarse-grained access.
136
167
 
137
168
  ```typescript
138
- if (hearth.hasRole("billing-admin")) {
169
+ if (await hearth.hasRole("billing-admin")) {
139
170
  renderBillingPanel();
140
171
  }
141
172
  ```
142
173
 
143
- ### `inGroup(group: string): boolean`
174
+ ### `inGroup(group: string): Promise<boolean>`
144
175
 
145
- Returns `true` iff the JWT `groups` claim contains the group slug.
176
+ Resolves `true` iff the token verifies and its `groups` claim contains the group slug.
146
177
 
147
178
  ```typescript
148
- if (hearth.inGroup("engineering")) {
179
+ if (await hearth.inGroup("engineering")) {
149
180
  renderInternalToolingLink();
150
181
  }
151
182
  ```
152
183
 
153
- ### `inOrg(org: string): boolean`
184
+ ### `inOrg(org: string): Promise<boolean>`
154
185
 
155
- Returns `true` iff the JWT `oid` claim equals the given org ID.
186
+ Resolves `true` iff the token verifies and its `oid` claim equals the given org ID.
156
187
 
157
188
  ```typescript
158
- if (hearth.inOrg("org_acme")) {
189
+ if (await hearth.inOrg("org_acme")) {
159
190
  renderAcmeContent();
160
191
  }
161
192
  ```
162
193
 
163
194
  ### `client.permissions(): Promise<MePermissionsResponse>`
164
195
 
165
- Calls `GET /v1/me/permissions` and returns the **freshly-resolved** RBAC claim set from the server. Unlike the synchronous helpers above, this reflects any role/group assignments made since the JWT was issued.
196
+ Calls `GET /v1/me/permissions` and returns the **freshly-resolved** RBAC claim set from the server. Unlike the predicates above, this reflects any role/group assignments made since the JWT was issued.
166
197
 
167
198
  ```typescript
168
199
  const { roles, groups, permissions } = await hearth.client.permissions();
169
200
  ```
170
201
 
171
- Use `client.permissions()` when you need post-issuance accuracy (e.g., after an admin operation). For every other check, prefer the synchronous local helpers — they're faster and don't touch the network.
202
+ Use `client.permissions()` when you need post-issuance accuracy (e.g., after an admin operation). For every other check, prefer the local predicates — once the JWKS is cached they make no network call.
172
203
 
173
204
  ---
174
205
 
@@ -190,7 +221,8 @@ import {
190
221
  const hearth = createHearth({
191
222
  baseUrl: "https://hearth.example.com",
192
223
  realmId: "<your-realm-id>",
193
- getToken: () => localStorage.getItem("access_token"),
224
+ issuerUrl: "https://hearth.example.com/realms/<realm-slug>",
225
+ getToken: () => auth.getAccessToken(), // auth = createHearthAuth(...)
194
226
  });
195
227
 
196
228
  // 2. Mount the provider at the root of your React tree
@@ -220,7 +252,7 @@ function NavBar() {
220
252
  }
221
253
  ```
222
254
 
223
- All hooks return `false` when no `HearthProvider` is mounted, making them safe to call in tests without a provider.
255
+ The hooks return `boolean`. Each runs the facade's asynchronous check after every render and returns its last result: `false` until the check resolves, and `false` when it rejects. All hooks return `false` when no `HearthProvider` is mounted, making them safe to call in tests without a provider.
224
256
 
225
257
  ---
226
258
 
@@ -259,7 +291,8 @@ const delta = await client.svDelta(serviceToken, snap.current_seq, 500); // null
259
291
  const discovery = await client.discover();
260
292
 
261
293
  // Verify an access token: EdDSA signature against the realm JWKS, then exp,
262
- // nbf, iss (must equal issuerUrl) and aud (must contain clientId, when set).
294
+ // nbf, iat (not in the future), iss (must equal issuerUrl) and aud (must
295
+ // contain `audience`, default "hearth").
263
296
  const claims = await client.verifyToken(accessToken);
264
297
  claims.subject(); // sub
265
298
  claims.scopes(); // scope split into an array
@@ -365,13 +398,13 @@ Errors raised by the SDK itself extend `HearthSdkError`:
365
398
 
366
399
  | Error | When |
367
400
  |---|---|
368
- | `ConfigurationError` | A required setting is missing (`clientId`, `realmId`, a discovery endpoint) |
401
+ | `ConfigurationError` | A required setting is missing (`clientId`, `realmId`, a discovery endpoint) or `audience` is empty |
369
402
  | `DiscoveryError` | The discovery document cannot be fetched or is invalid |
370
403
  | `JWKSFetchError` | The JWKS cannot be fetched |
371
404
  | `TokenVerificationError` | Base class of every token failure below |
372
- | `TokenExpiredError`, `TokenNotYetValidError` | `exp` / `nbf` outside the clock-skew window |
405
+ | `TokenExpiredError`, `TokenNotYetValidError` | `exp` / `nbf` or `iat` outside the clock-skew window |
373
406
  | `TokenInvalidError` | Bad signature, wrong algorithm, malformed JWT |
374
- | `TokenIssuerError`, `TokenAudienceError` | `iss` / `aud` mismatch |
407
+ | `TokenIssuerError`, `TokenAudienceError` | `iss` mismatch / `aud` does not contain the configured `audience` |
375
408
  | `IntrospectionError` | The introspection request failed or returned non-JSON |
376
409
  | `OAuthFlowError` | A token, userinfo, permissions or session-version request failed (`statusCode`, `errorCode`) |
377
410
  | `AuthorizationModeMismatchError` | Introspection echoed a mode other than `expectedMode` |
@@ -416,7 +449,8 @@ const admin = new AdminClient("http://127.0.0.1:8420", realm_id, access_token);
416
449
  // HearthClientConfig — constructor argument for HearthClient
417
450
  interface HearthClientConfig {
418
451
  issuerUrl: string; // e.g. "https://hearth.example.com"; endpoints are discovered from it
419
- clientId?: string; // needed for login flows, introspection; pins `aud` on verifyToken
452
+ clientId?: string; // needed for login flows, introspection
453
+ audience?: string; // expected `aud` on verifyToken; default "hearth"; always checked
420
454
  clientSecret?: string; // confidential clients only
421
455
  realmId?: string; // sent as X-Realm-ID; needed by authorize, mePermissions, sv feed, magic link
422
456
  httpTimeout?: number; // ms, default 10 000
@@ -429,18 +463,47 @@ interface HearthClientConfig {
429
463
  interface HearthOptions {
430
464
  baseUrl: string;
431
465
  realmId: string;
432
- getToken: () => string | null | undefined; // called on every predicate check
466
+ issuerUrl: string; // the realm issuer, e.g. "https://hearth.example.com/realms/acme"
467
+ audience?: string; // expected `aud`; default "hearth"
468
+ // called on every predicate check
469
+ getToken: () => string | null | undefined | Promise<string | null | undefined>;
470
+ sessionVersions?: SessionVersionConfig;
433
471
  }
434
472
 
435
- // HearthFacade — returned by createHearth()
473
+ // HearthFacade — returned by createHearth(); each predicate verifies the token first
436
474
  interface HearthFacade {
437
- hasPermission(permission: string): boolean;
438
- hasRole(role: string): boolean;
439
- inGroup(group: string): boolean;
440
- inOrg(org: string): boolean;
475
+ hasPermission(permission: string): Promise<boolean>;
476
+ hasRole(role: string): Promise<boolean>;
477
+ inGroup(group: string): Promise<boolean>;
478
+ inOrg(org: string): Promise<boolean>;
479
+ sessionVersionCacheAge(): number;
480
+ stop(): void;
441
481
  client: { permissions(): Promise<MePermissionsResponse> };
442
482
  }
443
483
 
484
+ // AuthConfig — second argument to createHearthAuth(client, config)
485
+ interface AuthConfig {
486
+ clientId: string;
487
+ redirectUri: string;
488
+ hearthUrl: string; // e.g. "https://hearth.example.com"
489
+ realmSlug: string; // e.g. "acme"
490
+ storage?: "sessionStorage" | "localStorage" | "memory" | TokenStorage; // default "sessionStorage"
491
+ storageKeyPrefix?: string; // default "hearth_"
492
+ }
493
+
494
+ // HearthBrowserAuth — returned by createHearthAuth()
495
+ interface HearthBrowserAuth {
496
+ startLogin(): Promise<void>;
497
+ handleCallback(code: string, state: string): Promise<void>;
498
+ refreshAccessToken(): Promise<void>;
499
+ logout(): Promise<void>;
500
+ getAccessToken(): string | null; // held in memory
501
+ getRefreshToken(): string | null;
502
+ getIdToken(): string | null;
503
+ isAuthenticated(): boolean;
504
+ clearTokens(): void;
505
+ }
506
+
444
507
  // AuthorizeParams
445
508
  interface AuthorizeParams {
446
509
  clientId: string;
@@ -550,7 +613,7 @@ class HearthError extends Error {
550
613
 
551
614
  **`TokenInvalidError`** — JWT signature does not match any key in the JWKS. If the server recently rotated keys the SDK will re-fetch once automatically; persistent failures indicate a key mismatch.
552
615
 
553
- **`TokenAudienceError`** — the token's `aud` claim does not contain the configured audience. Verify `clientId` matches the audience your authorization server issues.
616
+ **`TokenAudienceError`** — the token's `aud` claim does not contain the configured `audience` (default `"hearth"`). Set `audience` to the resource URI the token was issued for; the client ID is not the audience.
554
617
 
555
618
  **`AuthorizationModeMismatchError`** — the server echoed an `access_token_authorization` mode
556
619
  that differs from the SDK's `expectedMode` config or the `mode` passed to `requirePermission`.
@@ -569,7 +632,7 @@ registering the OAuth client; the SDK validates you stay consistent.
569
632
 
570
633
  RBAC claims (`permissions`, `roles`, `groups`) are embedded in the JWT at issuance. The
571
634
  checker verifies the token first — EdDSA signature against the realm's JWKS, plus `exp`,
572
- `nbf`, `iss` and (when `clientId` is set) `aud` — and only then reads the claim. The JWKS
635
+ `nbf`, `iat`, `iss` and `aud` — and only then reads the claim. The JWKS
573
636
  is cached, so after the first request there is no network traffic per check.
574
637
 
575
638
  A token that does not verify returns `false`; the checker never trusts an unverified
@@ -580,7 +643,7 @@ import { HearthClient, requirePermission } from "@hearth-auth/sdk";
580
643
 
581
644
  const client = new HearthClient({
582
645
  issuerUrl: "https://auth.example.com",
583
- clientId: "<your-client-id>", // enables `aud` pinning
646
+ audience: "https://api.example.com", // expected `aud`; default "hearth"
584
647
  });
585
648
 
586
649
  const check = requirePermission("docs.write", { mode: "embedded", client });
@@ -659,7 +722,7 @@ const allowed = await check(accessToken);
659
722
  import express from "express";
660
723
  import { HearthClient, hearthMiddleware } from "@hearth-auth/sdk";
661
724
 
662
- const client = new HearthClient({ issuerUrl: "https://hearth.example.com", clientId: "my-api" });
725
+ const client = new HearthClient({ issuerUrl: "https://hearth.example.com" });
663
726
  const app = express();
664
727
 
665
728
  app.get("/docs", hearthMiddleware({ client, requiredPermission: "docs.read" }), (req, res) => {
@@ -1,10 +1,17 @@
1
- import { HearthApiClient } from "./client.js";
2
- export declare function getAccessToken(): string | null;
3
- export declare function getRefreshToken(): string | null;
4
- export declare function getIdToken(): string | null;
5
- /** True iff an access token is present and not yet expired. */
6
- export declare function isAuthenticated(): boolean;
7
- export declare function clearTokens(): void;
1
+ import type { HearthApiClient } from "./client.js";
2
+ /** A key/value store for the SDK's browser state. `Storage` satisfies it. */
3
+ export interface TokenStorage {
4
+ getItem(key: string): string | null;
5
+ setItem(key: string, value: string): void;
6
+ removeItem(key: string): void;
7
+ }
8
+ /**
9
+ * Where {@link createHearthAuth} keeps its state: `"sessionStorage"`
10
+ * (default), `"localStorage"` (survives the tab closing; readable by any
11
+ * script on the origin), `"memory"` (lost on reload or redirect), or a custom
12
+ * {@link TokenStorage}.
13
+ */
14
+ export type AuthStorage = "sessionStorage" | "localStorage" | "memory" | TokenStorage;
8
15
  /** Configuration for {@link createHearthAuth}. */
9
16
  export interface AuthConfig {
10
17
  /** OAuth 2.0 client ID. */
@@ -15,18 +22,44 @@ export interface AuthConfig {
15
22
  hearthUrl: string;
16
23
  /** Realm name (slug), e.g. `"demo"`. */
17
24
  realmSlug: string;
25
+ /**
26
+ * Where to keep the refresh token, ID token and in-flight login state.
27
+ * Default `"sessionStorage"`. `"memory"` does not survive the login
28
+ * redirect, so use it only with a custom flow that stays on the page.
29
+ */
30
+ storage?: AuthStorage;
31
+ /** Prefix of every storage key the SDK writes. Default `"hearth_"`. */
32
+ storageKeyPrefix?: string;
18
33
  }
19
34
  /** Auth facade returned by {@link createHearthAuth}. */
20
35
  export interface HearthBrowserAuth {
36
+ /** Redirect to the authorization endpoint with a fresh PKCE challenge. */
21
37
  startLogin(): Promise<void>;
38
+ /** Check `state`, then exchange the callback's code for tokens. */
22
39
  handleCallback(code: string, state: string): Promise<void>;
40
+ /** Exchange the stored refresh token for a new access token. */
23
41
  refreshAccessToken(): Promise<void>;
42
+ /** Clear the local session and redirect to the realm's end-session endpoint. */
24
43
  logout(): Promise<void>;
44
+ /** The current access token (held in memory), or `null`. */
45
+ getAccessToken(): string | null;
46
+ /** The stored refresh token, or `null`. */
47
+ getRefreshToken(): string | null;
48
+ /** The stored ID token, or `null`. */
49
+ getIdToken(): string | null;
50
+ /** True iff an access token is present and not yet expired. */
51
+ isAuthenticated(): boolean;
52
+ /** Drop every token, and cancel the scheduled refresh. */
53
+ clearTokens(): void;
25
54
  }
26
55
  /**
27
56
  * Create a browser-side Hearth auth facade backed entirely by the SDK.
28
57
  *
29
58
  * Handles the full PKCE login flow, token storage, silent refresh, and
30
59
  * RP-initiated logout. No custom crypto or OIDC endpoint logic required.
60
+ *
61
+ * At creation it removes the refresh and ID tokens earlier SDK releases kept
62
+ * in `localStorage`, unless `localStorage` is the configured store and those
63
+ * are its own keys.
31
64
  */
32
65
  export declare function createHearthAuth(client: HearthApiClient, config: AuthConfig): HearthBrowserAuth;
@@ -1,70 +1,100 @@
1
1
  import { startLogin } from "./pkce.js";
2
- // ── Token store ─────────────────────────────────────────────────────────────
3
- // Access token lives in memory only. Refresh + ID tokens survive page reloads
4
- // via localStorage. For stricter XSS safety, swap for an HttpOnly-cookie BFF.
5
- const REFRESH_KEY = "hearth_refresh_token";
6
- const ID_KEY = "hearth_id_token";
7
- let _accessToken = null;
8
- let _expiresAt = null;
9
- let _refreshTimer = null;
10
- export function getAccessToken() {
11
- return _accessToken;
12
- }
13
- export function getRefreshToken() {
14
- return localStorage.getItem(REFRESH_KEY);
15
- }
16
- export function getIdToken() {
17
- return localStorage.getItem(ID_KEY);
18
- }
19
- /** True iff an access token is present and not yet expired. */
20
- export function isAuthenticated() {
21
- return _accessToken !== null && _expiresAt !== null && Date.now() / 1000 < _expiresAt;
22
- }
23
- export function clearTokens() {
24
- _accessToken = null;
25
- _expiresAt = null;
26
- localStorage.removeItem(REFRESH_KEY);
27
- localStorage.removeItem(ID_KEY);
28
- if (_refreshTimer !== null) {
29
- clearTimeout(_refreshTimer);
30
- _refreshTimer = null;
31
- }
32
- }
33
- function storeTokens(tokens, fallbackRefresh) {
34
- _accessToken = tokens.access_token;
35
- _expiresAt = Date.now() / 1000 + (tokens.expires_in ?? 3600);
36
- const rt = tokens.refresh_token ?? fallbackRefresh;
37
- if (rt)
38
- localStorage.setItem(REFRESH_KEY, rt);
39
- if (tokens.id_token)
40
- localStorage.setItem(ID_KEY, tokens.id_token);
2
+ /** Default prefix of every storage key the SDK writes. */
3
+ const DEFAULT_PREFIX = "hearth_";
4
+ /** Keys earlier SDK releases wrote to `localStorage`, removed at init. */
5
+ const LEGACY_LOCAL_STORAGE_KEYS = ["hearth_refresh_token", "hearth_id_token"];
6
+ function memoryStorage() {
7
+ const data = new Map();
8
+ return {
9
+ getItem: (key) => data.get(key) ?? null,
10
+ setItem: (key, value) => {
11
+ data.set(key, value);
12
+ },
13
+ removeItem: (key) => {
14
+ data.delete(key);
15
+ },
16
+ };
41
17
  }
42
- function scheduleRefresh(expiresIn, doRefresh) {
43
- if (_refreshTimer !== null)
44
- clearTimeout(_refreshTimer);
45
- const delayMs = Math.max(expiresIn * 0.8, expiresIn - 60) * 1000;
46
- _refreshTimer = setTimeout(() => {
47
- void doRefresh().catch(() => {
48
- /* re-auth on next action */
49
- });
50
- }, delayMs);
18
+ function resolveStorage(option) {
19
+ if (option === undefined || option === "sessionStorage")
20
+ return sessionStorage;
21
+ if (option === "localStorage")
22
+ return localStorage;
23
+ if (option === "memory")
24
+ return memoryStorage();
25
+ return option;
51
26
  }
52
- const VERIFIER_KEY = "hearth_pkce_verifier";
53
- const STATE_KEY = "hearth_oauth_state";
54
27
  /**
55
28
  * Create a browser-side Hearth auth facade backed entirely by the SDK.
56
29
  *
57
30
  * Handles the full PKCE login flow, token storage, silent refresh, and
58
31
  * RP-initiated logout. No custom crypto or OIDC endpoint logic required.
32
+ *
33
+ * At creation it removes the refresh and ID tokens earlier SDK releases kept
34
+ * in `localStorage`, unless `localStorage` is the configured store and those
35
+ * are its own keys.
59
36
  */
60
37
  export function createHearthAuth(client, config) {
38
+ const store = resolveStorage(config.storage);
39
+ const prefix = config.storageKeyPrefix ?? DEFAULT_PREFIX;
40
+ const key = {
41
+ refresh: `${prefix}refresh_token`,
42
+ id: `${prefix}id_token`,
43
+ verifier: `${prefix}pkce_verifier`,
44
+ state: `${prefix}oauth_state`,
45
+ };
46
+ if (typeof localStorage !== "undefined") {
47
+ const ownKeys = store === localStorage ? [key.refresh, key.id] : [];
48
+ for (const legacy of LEGACY_LOCAL_STORAGE_KEYS) {
49
+ if (!ownKeys.includes(legacy))
50
+ localStorage.removeItem(legacy);
51
+ }
52
+ }
53
+ let accessToken = null;
54
+ let expiresAt = null;
55
+ let refreshTimer = null;
56
+ function getRefreshToken() {
57
+ return store.getItem(key.refresh);
58
+ }
59
+ function getIdToken() {
60
+ return store.getItem(key.id);
61
+ }
62
+ function clearTokens() {
63
+ accessToken = null;
64
+ expiresAt = null;
65
+ store.removeItem(key.refresh);
66
+ store.removeItem(key.id);
67
+ if (refreshTimer !== null) {
68
+ clearTimeout(refreshTimer);
69
+ refreshTimer = null;
70
+ }
71
+ }
72
+ function storeTokens(tokens, fallbackRefresh) {
73
+ accessToken = tokens.access_token;
74
+ expiresAt = Date.now() / 1000 + (tokens.expires_in ?? 3600);
75
+ const rt = tokens.refresh_token ?? fallbackRefresh;
76
+ if (rt)
77
+ store.setItem(key.refresh, rt);
78
+ if (tokens.id_token)
79
+ store.setItem(key.id, tokens.id_token);
80
+ }
81
+ function scheduleRefresh(expiresIn) {
82
+ if (refreshTimer !== null)
83
+ clearTimeout(refreshTimer);
84
+ const delayMs = Math.max(expiresIn * 0.8, expiresIn - 60) * 1000;
85
+ refreshTimer = setTimeout(() => {
86
+ void refreshAccessToken().catch(() => {
87
+ /* re-auth on next action */
88
+ });
89
+ }, delayMs);
90
+ }
61
91
  async function refreshAccessToken() {
62
92
  const rt = getRefreshToken();
63
93
  if (!rt)
64
94
  throw new Error("No refresh token stored");
65
95
  const tokens = await client.refreshTokens(config.clientId, rt);
66
96
  storeTokens(tokens, rt);
67
- scheduleRefresh(tokens.expires_in ?? 3600, refreshAccessToken);
97
+ scheduleRefresh(tokens.expires_in ?? 3600);
68
98
  }
69
99
  return {
70
100
  async startLogin() {
@@ -72,15 +102,15 @@ export function createHearthAuth(client, config) {
72
102
  clientId: config.clientId,
73
103
  redirectUri: config.redirectUri,
74
104
  });
75
- sessionStorage.setItem(VERIFIER_KEY, codeVerifier);
76
- sessionStorage.setItem(STATE_KEY, state);
105
+ store.setItem(key.verifier, codeVerifier);
106
+ store.setItem(key.state, state);
77
107
  window.location.href = url;
78
108
  },
79
109
  async handleCallback(_code, state) {
80
- const storedState = sessionStorage.getItem(STATE_KEY);
81
- const codeVerifier = sessionStorage.getItem(VERIFIER_KEY) ?? undefined;
82
- sessionStorage.removeItem(STATE_KEY);
83
- sessionStorage.removeItem(VERIFIER_KEY);
110
+ const storedState = store.getItem(key.state);
111
+ const codeVerifier = store.getItem(key.verifier) ?? undefined;
112
+ store.removeItem(key.state);
113
+ store.removeItem(key.verifier);
84
114
  if (storedState !== state)
85
115
  throw new Error("State mismatch — possible CSRF");
86
116
  const tokens = await client.handleCallback({
@@ -90,7 +120,7 @@ export function createHearthAuth(client, config) {
90
120
  codeVerifier,
91
121
  });
92
122
  storeTokens(tokens);
93
- scheduleRefresh(tokens.expires_in ?? 3600, refreshAccessToken);
123
+ scheduleRefresh(tokens.expires_in ?? 3600);
94
124
  },
95
125
  refreshAccessToken,
96
126
  async logout() {
@@ -104,6 +134,11 @@ export function createHearthAuth(client, config) {
104
134
  params.set("id_token_hint", idToken);
105
135
  window.location.href = `${end}?${params}`;
106
136
  },
137
+ getAccessToken: () => accessToken,
138
+ getRefreshToken,
139
+ getIdToken,
140
+ isAuthenticated: () => accessToken !== null && expiresAt !== null && Date.now() / 1000 < expiresAt,
141
+ clearTokens,
107
142
  };
108
143
  }
109
144
  //# sourceMappingURL=browser-auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser-auth.js","sourceRoot":"","sources":["../src/browser-auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"browser-auth.js","sourceRoot":"","sources":["../src/browser-auth.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,14 @@ export interface HearthClientConfig {
14
14
  * Required for flows that need a client identity (e.g. introspection).
15
15
  */
16
16
  clientId?: string;
17
+ /**
18
+ * Expected `aud` of the access tokens {@link HearthClient.verifyToken}
19
+ * accepts: the name of this API (RFC 9068 §4), not the client ID.
20
+ * Default: `"hearth"`, the audience Hearth mints when a client names no
21
+ * resource. An API registered as a protected resource sets its resource URI.
22
+ * The check is always on.
23
+ */
24
+ audience?: string;
17
25
  /**
18
26
  * OAuth 2.0 client secret.
19
27
  * Required for confidential client flows (e.g. introspection).
@@ -77,6 +85,8 @@ export declare class HearthClient {
77
85
  readonly issuerUrl: string;
78
86
  readonly clientId: string | undefined;
79
87
  readonly clientSecret: string | undefined;
88
+ /** Expected `aud` of verified access tokens. Default `"hearth"`. */
89
+ readonly audience: string;
80
90
  readonly jwksTtl: number | undefined;
81
91
  readonly introspectionEndpointOverride: string | undefined;
82
92
  /** HTTP timeout in milliseconds applied to all outbound fetch calls. */
@@ -160,14 +170,16 @@ export declare class HearthClient {
160
170
  * 2. `exp` claim (rejects expired tokens).
161
171
  * 3. `nbf` claim (rejects post-dated tokens).
162
172
  * 4. `iss` claim (must match configured `issuerUrl`).
163
- * 5. `aud` claim (validated when `clientId` is set in config).
173
+ * 5. `aud` claim (must contain the configured `audience`, default `"hearth"`).
174
+ * 6. `iat` claim (rejects a token issued in the future).
164
175
  *
165
- * `exp` and `nbf` allow a 5-second clock skew.
176
+ * `exp`, `nbf` and `iat` allow a 5-second clock skew.
166
177
  *
167
178
  * @throws {@link TokenExpiredError} — token is expired.
179
+ * @throws {@link TokenNotYetValidError} — `nbf` or `iat` is in the future.
168
180
  * @throws {@link TokenInvalidError} — signature invalid or JWT malformed.
169
181
  * @throws {@link TokenIssuerError} — issuer does not match `issuerUrl`.
170
- * @throws {@link TokenAudienceError} — audience does not include `clientId`.
182
+ * @throws {@link TokenAudienceError} — audience does not include `audience`.
171
183
  * @throws {@link JWKSFetchError} — JWKS endpoint unreachable.
172
184
  */
173
185
  verifyToken(token: string): Promise<Claims>;