Part 64: Securely Storing Authentication Tokens in Next.js with Cookies

[Pages] Authentication

[Pages] Authentication

In the development of our SignInPage, we've reached a crucial step: handling the JSON web token (JWT) received from our API upon successful login. Properly managing this token is essential for maintaining user authentication across different API requests. Today, we'll explore the best practices for storing this token securely, and how to implement these practices using cookies and API routes in a Next.js application.

Why Store Tokens Securely?

When a user logs in, our API sends back a JWT that serves as proof of authentication. This token needs to be sent with subsequent API requests to authenticate the user. However, storing this token securely is critical because:

  • Security Risks: If stored insecurely, such as in Local Storage, the token can be accessed by any JavaScript running on the page, potentially exposing it to malicious scripts.

  • Persistent Authentication: Proper storage ensures that a user remains authenticated across sessions.

Choosing Cookies Over Local Storage

We have two main options for storing the JWT: Local Storage and cookies. While Local Storage is convenient, it's not suitable for sensitive data due to security vulnerabilities. Instead, we'll use cookies because they offer enhanced security features.

Why Cookies?

  • HttpOnly Cookies: These cookies can only be accessed by the server, not by client-side JavaScript, reducing the risk of token theft via cross-site scripting (XSS) attacks.

  • Secure and SameSite Flags: Additional cookie attributes can enhance security by ensuring the cookie is only sent over HTTPS and restricts cross-site usage.

Implementing a Secure API Route in Next.js

To store the token in a cookie, we need to set up an API route in our Next.js application.

Step 1: Create an API Route

First, we create an API route to handle login requests and set cookies.

// pages/api/login.js

function handleLogin(req, res) {
  if (req.method !== 'POST') {
    res.status(405).end(); // Method Not Allowed
    return;
  }

  console.log('req.body:', req.body);
  // Here you would normally authenticate the user and get the JWT
  const token = 'your-jwt-token'; // Placeholder for the actual token

  // Set the cookie with the token
  res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Path=/`);

  res.status(200).json({ message: 'Logged in successfully' });
}

export default handleLogin;

Explanation:

  • API Route: Located in pages/api/login.js, this route handles login requests.

  • Method Check: We only accept POST requests. If a request uses any other method, we return a 405 status.

  • Logging and Token Handling: For demonstration, the request body is logged, and a placeholder token is set as a cookie.

  • Setting Cookies: We use the Set-Cookie header to store the token. The HttpOnly attribute ensures it's inaccessible to JavaScript.

Step 2: Test the API Route

To ensure our API route functions as expected, we can use a REST client to send requests.

  • GET Request: Sending a GET request should return a "405 Method Not Allowed" response.

  • POST Request: When a POST request with email and password is sent, the response should be "200 OK" with the token set as a cookie.

Accessing Request Data

The req.body property allows us to access the data sent in the request body. In our example, this includes the user's email and password, which we log to the console for verification.

Conclusion

By using cookies to store JWTs, we've enhanced the security of our authentication mechanism in Next.js. This method ensures that sensitive tokens are protected from client-side JavaScript, offering a more secure user experience. In the next steps, we will integrate the CMS API calls within our handler to complete the authentication flow. Stay tuned for more on building secure and efficient web applications!

Last updated