@broberg/sso 0.10.0 → 0.10.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.
Files changed (2) hide show
  1. package/README.md +23 -9
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -395,8 +395,9 @@ app.get("/api/something", sso.require, async (c) => {
395
395
  Delete rows older than `sessionMaxAge` (`SSO_SESSION_MAX_AGE`, default 7 days)
396
396
  with a periodic job or a TTL — e.g. store a `createdAt` beside the row and
397
397
  `DELETE FROM tokens WHERE created_at < now - sessionMaxAge`, or a Redis
398
- `EX sessionMaxAge`. It matters because the row would hold a **refresh token**
399
- if BID ever issues one, and a forgotten refresh token is a live credential.
398
+ `EX sessionMaxAge`. It matters because with `offline_access` the row holds a
399
+ **refresh token**, and a forgotten refresh token is a live credential until
400
+ the user's BID sign-in ends.
400
401
  - **Since 0.10.0 the failures are `SsoReauthError`** (a subclass of `SsoError`,
401
402
  so existing `catch` blocks still match) with a `reason`: `no_session`,
402
403
  `no_tokens`, `expired` or `refresh_failed`. Every one of them means "log in
@@ -444,13 +445,26 @@ logged in before you added it gets `403 insufficient_scope` until she logs in ag
444
445
  | 4xx | `{"error":"<BID's code>"}` | BID refused the value (e.g. an invalid name) |
445
446
  | 502 | `{"error":"bid_unavailable"}` | BID did not answer usably |
446
447
 
447
- **`reauth` is the NORMAL case after an hour, not an edge case.** BID issues apps
448
- a one-hour access token and — measured in production, 2026-10-05 — **no refresh
449
- token**. So `getAccessToken` cannot renew, and an hour after login every
450
- account-page call answers `401 {"error":"reauth"}`. Build the page for that: a
451
- calm «Log ind igen» that goes to `/auth/login?returnTo=<the account page>`, not
452
- an error. The same answer comes back when BID itself refuses the token (a 401
453
- from BID — revoked, or the session was ended in BID). It is never a 500.
448
+ **Renewal needs `offline_access`, in two places.** BID's access token lives one
449
+ hour. Since 2026-10-06 (broberg-id F084.156) BID issues a **refresh token**, but
450
+ only when your app's BID registration has `offline_access` (ask broberg-id, per
451
+ app) **and** you request it at login:
452
+ `SSO_SCOPES="openid profile email profile:write offline_access"`. Then
453
+ `getAccessToken` renews by itself and the account page keeps working.
454
+ - **Without `offline_access`** there is no refresh token. An hour after login,
455
+ every account-page call answers `401 {"error":"reauth"}`.
456
+ - **With it, renewal still ends with the user's BID sign-in.** Log ud, «Log ud
457
+ overalt», a new password or expiry make BID answer the renewal with
458
+ `400 invalid_grant`, and that also becomes `401 {"error":"reauth"}`.
459
+ - **Rotation:** every renewal issues a new refresh token, and presenting the old
460
+ one again revokes the whole chain. `getAccessToken` renews one session at a
461
+ time **per process**. On several machines, a race costs one «Log ind igen»,
462
+ never a stale token.
463
+
464
+ So `reauth` is a normal state, not an edge case. Build the page for it: a calm
465
+ «Log ind igen» that goes to `/auth/login?returnTo=<the account page>` (no
466
+ `prompt=login`), not an error. The same answer comes back when BID refuses the
467
+ access token itself (a 401 from BID). It is never a 500.
454
468
 
455
469
  **The core methods**, if you are not on Hono: `client.getProfile(token)`,
456
470
  `updateProfile(token, name)`, `uploadAvatar(token, bytes, contentType)`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@broberg/sso",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "The thin client for Broberg ID — send a user to central login, verify the ID token against JWKS, keep a local session. Public client + PKCE only: no passwords, no passkeys, no client secret can live in an app.",
5
5
  "type": "module",
6
6
  "license": "MIT",