Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 65

Browser Storage for Auth Tokens

Recommended resources

Hello and welcome back!

In the last lesson, we successfully built a login form and, upon a successful API response, stored the user's information using localStorage.setItem(). It was a quick but crucial step. Today, we're going to dive deep into that specific action.

Our learning outcome is to store and retrieve authentication tokens from browser storage (e.g., localStorage). We'll explore what localStorage is, how it works, and how it compares to its close relative, sessionStorage. Most importantly, you'll learn how to retrieve the stored token to make authenticated API requests, a fundamental skill for building any application with user accounts.

This lesson bridges the gap between logging a user in and actually using their authenticated status throughout the application.

1. Options for Storing Tokens in the Browser

When a server sends back an authentication token (like a JWT), the client-side application needs to store it somewhere to send it with subsequent requests. The browser provides several mechanisms for this.

The article below gives an excellent, practical overview of the most common methods, comparing their benefits and drawbacks, especially regarding persistence and security. As a developer, understanding these trade-offs is crucial for making informed architectural decisions.

How to Store Session Tokens in a Browser (and the impacts of each)

Please read the following sections from the article "How to Store Session Tokens in a Browser". It provides clear code examples and analysis for using localStorage and sessionStorage.

Read the sections titled 'Local Storage' and 'Session Storage'. As you read, focus on: The API: Notice the methods used: setItem() to store the token and how the token is retrieved to be placed in an Authorization header for a fetch request. Persistence: Pay close attention to the key difference described between them. localStorage persists when you close the browser, while sessionStorage does not. Security: Note the comments on the 'XSS Impact' for both. We'll discuss this more later.

As you saw, localStorage and sessionStorage are very similar. The main difference is the "lifetime" of the data:

  • sessionStorage: Data is cleared when the page session ends (i.e., when the browser tab is closed).
  • localStorage: Data has no expiration time; it remains until explicitly cleared via JavaScript or by the user clearing their browser cache.

For our project, we want the user to remain logged in even if they close and reopen the browser. Therefore, localStorage is the appropriate choice.


2. The Web Storage API in Practice

The Web Storage API is straightforward and consists of a few key methods. Let's formalize what you saw in the article.

Storing Data: setItem()

You've already seen this in action. It takes a key-value pair. Both must be strings. This is why we used JSON.stringify() in the last lesson to store the user object.

// The user object we get from the API
const result = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  token: 'some-long-jwt-string'
};

// Store the whole object as a JSON string
localStorage.setItem('user-info', JSON.stringify(result));

// Or, just store the token
localStorage.setItem('authToken', result.token);

Retrieving Data: getItem()

This is how we get the token back when our application loads or when we need to make an authenticated request. The MDN documentation provides the definitive syntax.

Storage: getItem() method - Web APIs | MDN

Let's quickly review the official documentation for the getItem() method.

Read the introduction and the 'Syntax' section. The key takeaway is that getItem() returns the string value if the key exists, or null if it doesn't.

Knowing that getItem() returns null for a non-existent key is very useful. We can use it as a simple check to see if a user is logged in.

const token = localStorage.getItem('authToken');

if (token) {
  console.log('User is logged in.');
  // We can now use this token for API calls
} else {
  console.log('No token found. User is not logged in.');
}

Removing Data: removeItem()

When a user logs out, we must remove their token from storage. The removeItem() method does exactly that.

// On logout
localStorage.removeItem('authToken');
localStorage.removeItem('user-info');

// Now, calling getItem('authToken') will return null

3. Using the Token for Authenticated Requests

Storing the token is only half the story. The real purpose is to send it back to the server with protected requests. The standard way to do this with token-based authentication is via the Authorization header, typically using the Bearer scheme.

The code snippet from the ropnop article you read earlier shows this perfectly. Here's a recap of that pattern:

// Function to fetch some protected data
async function getProtectedData() {
  const token = localStorage.getItem('authToken');
  
  // Prepare headers
  const headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  };

  // If a token exists, add the Authorization header
  if (token) {
    headers['Authorization'] = `Bearer ${token}`;
  }

  try {
    const response = await fetch('https://your-api.com/api/protected-route', {
      method: 'GET',
      headers: headers
    });

    if (!response.ok) {
      // Handle errors, e.g., if the token is expired (401 Unauthorized)
      throw new Error('Authentication failed');
    }

    const data = await response.json();
    console.log('Protected data:', data);
    return data;
  } catch (error) {
    console.error('Error fetching data:', error);
    // Maybe redirect to login page
  }
}

This pattern is fundamental. Your Laravel backend will be configured to look for this Authorization header, validate the JWT, and identify the user making the request.


4. A Note on Security: XSS

As the ropnop article pointed out, localStorage is vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker can inject malicious JavaScript onto your site, that script can read everything in localStorage and send the user's auth token to the attacker's server.

While this is a serious concern, for many applications, localStorage is considered an acceptable trade-off between security and convenience, provided you take other measures to prevent XSS vulnerabilities in the first place (e.g., sanitizing user input). The alternatives, like HttpOnly cookies, solve the XSS read problem but introduce others, like CSRF (Cross-Site Request Forgery), and are more complex to manage in a SPA-to-API architecture, especially across different domains. For the scope of this course, we will continue with localStorage as it's a common and instructive pattern for React SPAs.


Conclusion

You now have a solid understanding of how to manage authentication tokens in the browser. This is a critical piece of the puzzle that makes single-page applications feel persistent and secure.

Key Takeaways:

  • localStorage and sessionStorage are the two main browser storage mechanisms. localStorage persists across browser sessions, making it ideal for "remember me" functionality.
  • The API is simple: setItem(key, value), getItem(key), and removeItem(key).
  • When retrieving an item, you get the string value or null if the key doesn't exist.
  • To authenticate API calls, you retrieve the token from localStorage and include it in the Authorization: Bearer <token> header of your fetch request.
  • While convenient, localStorage is vulnerable to XSS attacks, so it's important to be aware of the security trade-offs.

Preview of the Next Lesson:

We can now store and retrieve the token, but each component that needs it would have to read from localStorage independently. This is not ideal. We need a single source of truth for our app's authentication state. In the next lesson, we will create a global AuthContext to provide authentication status and user data throughout the entire app. This context will initialize its state by reading from localStorage, making the user's login status easily accessible to any component that needs it.