DevLab
Encoding

JWT Refresh Tokens Explained

Learn how refresh tokens extend JWT authentication sessions securely, without requiring users to log in again every 15 minutes.

The Problem with Short-Lived JWTs

JWTs cannot be revoked before they expire — a fundamental property of stateless tokens. This forces a choice: long-lived tokens (security risk if stolen) or short-lived tokens (users get logged out constantly). Refresh tokens solve this tension.

How Refresh Tokens Work

  1. Login: Server issues a short-lived JWT (access token, 15 minutes) and a long-lived refresh token (7-30 days) stored in an httpOnly cookie.
  2. Normal requests: Client sends JWT in Authorization header. Server verifies without database lookup.
  3. When JWT expires: Client sends refresh token to a dedicated endpoint. Server checks it exists and has not been revoked, then issues a new JWT.

Refresh Token Rotation

The most secure pattern rotates the refresh token on every use — each refresh call returns a new refresh token and invalidates the old one. This detects theft: if an attacker uses a stolen token after the legitimate user already used it, the server sees a previously-used token and can invalidate the entire session.

// Rotation pattern (pseudocode)
POST /auth/refresh

Server:
1. Look up token in refresh_tokens table
2. If not found or already used: reject + log suspicious activity
3. Mark old token as used
4. Issue new refresh token + new JWT
5. Return both to client

Storage Rules

  • Refresh tokens — httpOnly, Secure, SameSite=Strict cookie (not readable by JavaScript)
  • Access JWT — memory or httpOnly cookie — never localStorage
  • Never store both in localStorage — a single XSS vulnerability gives an attacker unlimited session access

Revocation

Unlike JWTs, refresh tokens live in a database table, so revocation is instant: delete the row and the user is logged out on their next refresh cycle. This is how "log out all devices" works — delete all refresh tokens for that user ID.

Practice with these tools

More Learning Topics

RegexRegex Basics: A Complete Beginner's GuideRegexRegex Special Characters: Complete ReferenceRegexRegex Groups and Captures ExplainedRegexRegex Quantifiers: Complete GuideCSSCSS Selectors: The Complete GuideCSSCSS Specificity: Why Your Styles Aren't ApplyingJSONJSONPath Syntax: Query JSON Like XPathTimeUnix Timestamps ExplainedEncodingBase64 Encoding ExplainedEncodingJWT Structure and How It WorksEncodingJWT vs Session Tokens: Which Should You Use?CryptoHash Functions Explained: MD5, SHA-256, and When to Use EachEncodingURL Encoding Explained: What %20 Actually MeansJSONJSON Schema Explained: Validate Your JSON DataJSONJSON vs YAML: Which Should You Use?JSONJSON.stringify and JSON.parse: Edge Cases You Should KnowRegexRegex Lookahead and Lookbehind: Match Without ConsumingRegexRegex for Email Validation: The Right ApproachCSSThe CSS Box Model: margin, padding, border, and contentCSSFlexbox vs CSS Grid: When to Use EachCSSCSS Custom Properties (Variables) ExplainedTimeISO 8601 Explained: The Right Way to Format DatesTimeUnix Timestamps vs ISO 8601: Which to Use in Your API?EncodingUTF-8 Explained: How Computers Store TextTextCORS Explained: Why Your API Call is BlockedTextHTTP Status Codes: A Practical Developer GuideRegexNamed Capture Groups in Regex: Clean ExtractionsColorsColor Spaces Explained: RGB, HSL, HEX, and BeyondColorsColor Contrast for Developers: WCAG Rules and How to CheckNumbersNumber Bases Explained: Binary, Octal, Decimal, and HexNumbersBitwise Operations for Web DevelopersHTMLHTML Semantic Elements: A Complete ReferenceTextText Encoding for Developers: ASCII, UTF-8, and UnicodeRegexWhat is Regex? Complete Guide for DevelopersJSONJSON Format Explained: Structure, Syntax, and Common ErrorsEncodingHow JWT Works: Header, Payload, Signature DecodedHTMLHow to Convert Any Website to Markdown (for LLMs, RAG & Docs)TextPreparing Website Content for RAG: Clean Markdown Pipelines