Skip to main content

Refresh tokens

· 3 min read

This version introduces refresh tokens, reduces access tokens time to live, and removes any tokens from local storage.

Context: Why I Made This Change

With Spider, security and privacy are at the core of everything I build — especially when it comes to authentication.

Previously, Spider's authentication API issued long-lived access tokens that were stored in both memory and browser storage.
While functional, this approach posed several risks:

  • Long-lived tokens increase the blast radius of token leakage.
  • Revocation was difficult, requiring full token invalidation mechanisms.
  • XSS attacks were possible to fetch the token.

I wanted to align with modern best practices used by platforms like Google and GitHub:

  • Short-lived tokens
  • Secure, HttpOnly refresh tokens
  • Session isolation and rotation
  • Centralized revocation

How I did it: A Layered Security Redesign

I redesigned the authentication system around stateless access tokens and rotated refresh tokens, bound to individual sessions per device.

Key principles of the new system:

  1. Access tokens expire after a short time (~15 minutes).
  2. Refresh tokens are stored in HttpOnly, Secure cookies, inaccessible to JavaScript.
  3. Each session has a unique session ID, tracked securely in Redis.
  4. Refresh tokens are rotated at every use. Old ones are revoked immediately.
  5. Logout only revokes the current session, not all devices.
  6. Redis-backed store ensures tokens are centrally managed and revocable.

Schematic view

Login
└──> Provides:
├── Short-lived access token (JWT)
└── Refresh token (JWT in HttpOnly cookie)
└── Stored in Redis (indexed by jti and sessionId)

Access to UI
└──> Call `GET /sessions/token` to issue access token
├── Validates refresh token from cookie
├── Rotates token (new jti)
└── Issues new access token + refresh token

Access token close to expiry while on UI?
└──> Call `GET /sessions/token` to renew access token
└──> Renew impersonation and team tokens

User logs out
└──> Call `DELETE /sessions`
└── Uses access token to find sessionId
└── Revokes all refresh tokens in Redis for that session

What this means for you

  • More secure by default
    • Even if an access token leaks, it expires in minutes and cannot be refreshed without the securely stored cookie.
  • Improved session visibility
    • Every login from a browser or device is isolated, making future session management and revocation easier.

What's next

This foundation opens the door for future features:

  • User-facing device/session management UI
  • Login alerts for unknown devices
  • Optional 2FA challenges per session
  • Service accounts for scripts

I'm excited about what this unlocks and thankful to the community for helping Spider securely!