Skip to content

idp: token refresh for the programmatic flow — POST /idp/refresh #587

Description

@melvincarvalho

Found by plugin zero in production: Tideholm live on nostr.social uses the documented programmatic flow — POST /idp/credentials → Bearer token — as its browser login ("sign in with your pod"). The token dies at its fixed 3600s TTL and there is no way to renew it short of re-sending the password: mid-session, every request starts 401ing.

Tideholm mitigated app-side (it mints its own session cookie once getAgent verifies the identity), but that trade is only available to apps with their own session machinery, and it means the app session outlives the pod credential. The gap is JSS's to close: the programmatic flow is the only auth path a vanilla-JS app can realistically speak (full Solid-OIDC + DPoP is a heavy client lift), and it's the only path with no renewal story.

Proposal

POST /idp/refresh — authenticated by a still-valid Bearer token, returns a fresh token for the same WebID:

POST /idp/refresh
Authorization: Bearer <current, unexpired token>

→ { "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "webid": "..." }
  • Clients refresh proactively (e.g. at 80% of TTL) and a session lasts as long as the app is actually in use — an idle hour still ends it, preserving the short-TTL security posture.
  • Optional hardening: an absolute cap (e.g. refresh chains max out at 24h from the original credential grant), so a stolen token can't be kept alive forever. The original grant time can ride in the token payload.
  • Rate-limited like the other /idp/* endpoints.

Alternatives considered

  • Configurable TTL (createServer({ tokenTtl }) or an expires_in request param): simpler, but "long-lived bearer token in localStorage" is strictly worse than sliding renewal; could still be worth having for agent use-cases.
  • Refresh tokens (separate long-lived credential): the OIDC-proper answer, but heavier than this flow's spirit; /idp/credentials is deliberately the simple path.

Acceptance

  • POST /idp/refresh issues a fresh token from a valid one (401 on expired/invalid)
  • Optional absolute chain cap, documented
  • Rate limit consistent with /idp/credentials
  • docs/authentication.md updated; Tideholm's client switches from its cookie bridge to proactive refresh (or keeps both — cookie as offline fallback)

Metadata

Metadata

Assignees

No one assigned

    Labels

    pluginCould be implemented as a plugin (#206); core/plugin line defined in #564

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions