| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
The programmatic flow (POST /idp/credentials -> Bearer) is the only auth path a vanilla-JS app can realistically speak, and its 3600s tokens have no renewal story: mid-session every request starts 401ing. Found by plugin zero (Tideholm live on nostr.social) and inherited by bridge. POST /idp/refresh takes a still-valid, THIS-server-issued Bearer token (verified against our JWKS — not arbitrary credentials) and returns a fresh one for the same WebID. Clients refresh proactively (~80% of TTL) so an active session outlives the fixed TTL while an idle hour still ends it. An 'oat' (original auth time) claim, minted into credentials tokens and preserved across the chain, caps the whole chain absolutely (24h default, refreshMaxAge override) so a leaked token can't be renewed forever. Tests: fresh token authenticates; oat stable across a chain; chain cap enforced (invalid_grant); forged/foreign token refused; no-Bearer 401. docs/authentication.md updated.
There was a problem hiding this comment.
Adds a programmatic token refresh capability to JSS’s IdP so browser-based apps using POST /idp/credentials (Bearer flow) can renew tokens proactively without re-sending user passwords mid-session, while preserving the existing short TTL posture.
Changes:
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file| File | Description |
|---|---|
| test/idp-refresh.test.js | Adds tests covering refresh happy path, missing/invalid/foreign tokens, and refreshMaxAge behavior. |
| src/server.js | Wires refreshMaxAge from createServer() options into the IdP plugin. |
| src/idp/index.js | Registers the new POST /idp/refresh route with rate limiting and delegates to the handler. |
| src/idp/credentials.js | Adds oat to minted tokens and implements handleRefresh() logic and refreshMaxAge enforcement. |
| docs/authentication.md | Documents POST /idp/refresh, proactive refresh guidance, and refreshMaxAge. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sorry, something went wrong.
| if (!payload.webid || !payload.sub) { | ||
| return reply.code(401).send({ | ||
| error: 'invalid_token', | ||
| error_description: 'Token lacks the webid/sub claims required to refresh', | ||
| }); | ||
| } |
| return reply.code(401).send({ | ||
| error: 'invalid_token', | ||
| error_description: `Token is expired or not issued by this server: ${err.message}`, | ||
| }); |
| // Absolute lifetime of a refresh chain: a token may be renewed only within | ||
| // this window of its ORIGINAL password grant (oat), so a leaked token can't | ||
| // be kept alive indefinitely. 24h; overridable via createServer or env. | ||
| const DEFAULT_REFRESH_MAX_AGE = 24 * 60 * 60; |
| }); | ||
| assert.ok(whoami.status < 400, `refreshed token should authenticate, got ${whoami.status}`); | ||
| }); | ||
|
|
| Back | FazBrowse Home | New Git URL |
Closes #587. Found by plugin zero in production: Tideholm and bridge both use POST /idp/credentials → Bearer as their "sign in with your pod" flow, and the token dies at its fixed 3600s TTL with no way to renew short of re-sending the password — mid-session, every request starts 401ing.
The endpoint
POST /idp/refresh Authorization: Bearer <current, unexpired token> → { access_token, token_type: "Bearer", expires_in: 3600, webid }Same response shape as /idp/credentials. Clients refresh proactively (e.g. at 80% of TTL): an active session outlives the fixed TTL, while an idle hour still ends it — the short-TTL security posture is preserved.
Security
Acceptance
6 new tests in test/idp-refresh.test.js; full suite 1022/1022. The Tideholm/bridge clients can now switch from the cookie bridge to proactive refresh. Refs #206.