Autocodewizard Logo Working with APIs in SPAs - Autocodewizard Ebooks - Single Page Application

Chapter 8: Handling Authentication and Authorization

Understand how to implement secure user authentication and authorization flows in SPAs, including token management.

In this chapter, we’ll cover how to securely implement user authentication and authorization in Single Page Applications (SPAs). By the end of this chapter, you’ll understand core authentication concepts, including handling tokens, managing user sessions, and securing access to protected resources.

Authentication vs. Authorization

Authentication verifies a user’s identity (e.g., logging in), while authorization determines what the authenticated user can access. SPAs typically use authentication to verify users and then use authorization to control access to specific features or data.

Implementing Authentication with JWT

JSON Web Tokens (JWTs) are a popular solution for SPA authentication. After logging in, the server issues a JWT that the client stores and sends with each request to verify the user’s identity. Here’s a simple login flow using JWT:

fetch("https://api.example.com/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username: "user", password: "password" })
})
    .then(response => response.json())
    .then(data => {
        localStorage.setItem("token", data.token); // Save JWT to localStorage
    })
    .catch(error => console.error("Login failed:", error));

In this example, after successful login, the JWT is stored in localStorage for use in subsequent requests.

Storing and Using Tokens

Tokens can be stored in localStorage or sessionStorage. However, for better security, consider storing tokens in an HTTP-only cookie to prevent access by JavaScript and reduce the risk of XSS attacks. Here’s how you might use the token in subsequent API requests:

fetch("https://api.example.com/protected", {
    method: "GET",
    headers: {
        "Authorization": "Bearer " + localStorage.getItem("token")
    }
})
    .then(response => response.json())
    .then(data => console.log("Protected data:", data))
    .catch(error => console.error("Access denied:", error));

The token is added to the Authorization header for accessing protected endpoints.

Refreshing Tokens

Tokens have an expiration time. To maintain a user session, SPAs often use refresh tokens, which allow the app to request a new access token when the current one expires. A refresh token is typically stored securely (e.g., in an HTTP-only cookie) and used to obtain a new JWT without requiring the user to log in again.

// Example of refreshing an expired token
fetch("https://api.example.com/refresh-token", {
    method: "POST",
    credentials: "include"
})
    .then(response => response.json())
    .then(data => {
        localStorage.setItem("token", data.newToken);
    })
    .catch(error => console.error("Token refresh failed:", error));

This example uses a refresh token to obtain a new JWT and update it in localStorage.

Protecting Routes in SPAs

To secure certain parts of an SPA, you can implement route guards, which restrict access to routes based on the user’s authentication status. In frameworks like React, route guards are often created with conditional rendering:

function PrivateRoute({ children }) {
    const token = localStorage.getItem("token");
    return token ? children : ;
}

In this example, the component renders its children only if a valid token exists, otherwise redirecting the user to the login page.

Best Practices for Authentication and Authorization in SPAs

Summary and Next Steps

In this chapter, we covered authentication and authorization concepts for SPAs, including implementing JWT authentication, handling tokens, and protecting routes. In the next chapter, we’ll discuss performance optimization techniques to ensure your SPA runs efficiently and smoothly for users.