Part 66: Enhancing Your API with Secure Cookie Management and Environment Variables

[Pages] Authentication

[Pages] Authentication

In our previous discussions, we built a foundation for our API handler to authenticate users by sending login credentials to a CMS and returning user details or an error response. Now, let's take our API to the next level by securely managing cookies for session handling and utilizing environment variables for better configuration management.

Utilizing Environment Variables

Before diving into cookie management, let's ensure our code is clean and maintainable by using environment variables. This approach allows us to manage configuration details, such as CMS URLs, without hardcoding them into our application.

Step 1: Define the Environment Variable

In your .env file, define the CMS_URL variable:

CMS_URL=http://localhost:1337

Step 2: Use CMS_URL in API Calls

In our API handler, we can access this environment variable and use it in our API calls to the CMS. This approach ensures that the base URL for our CMS is easily configurable.

// pages/api/login.js

import cookie from 'cookie';
import { fetchJson } from '../../lib/api';

const { CMS_URL } = process.env;

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

  const { email, password } = req.body;

  try {
    const { jwt, user } = await fetchJson(`${CMS_URL}/auth/local`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ identifier: email, password }),
    });

    // TODO: Set JWT cookie

    res.status(200)
      .setHeader('Set-Cookie', cookie.serialize('jwt', jwt, {
        path: '/api',
        httpOnly: true,
      }))
      .json({
        id: user.id,
        name: user.username,
      });
  } catch (err) {
    res.status(401).end(); // Unauthorized
  }
}

export default handleLogin;

Securely Managing Cookies

Now that we've set up our environment variables, let's focus on securely storing the JSON Web Token (JWT) in a cookie. This method enhances security by restricting access to the token.

To format cookies correctly, we use a library that handles serialization for us. Install the cookie package:

npm install cookie

With the cookie library installed, we can serialize the JWT and set it as a cookie in our API response. The cookie should be set with specific attributes to ensure security.

  • Name and Value: The cookie's name is jwt, and its value is the JWT received from the CMS.

  • Path: Set to /api to restrict the cookie to API routes.

  • HttpOnly: Set to true to prevent JavaScript access, enhancing security.

Code Implementation

Here's how we set the cookie:

// pages/api/login.js

res.status(200)
  .setHeader('Set-Cookie', cookie.serialize('jwt', jwt, {
    path: '/api',
    httpOnly: true,
  }))
  .json({
    id: user.id,
    name: user.username,
  });

Testing the API Route

With our enhancements, let's test the API route to ensure it works seamlessly.

  1. Successful Login: A POST request with valid credentials should set the JWT cookie and return user details.

  2. Invalid Credentials: A 401 Unauthorized response is returned, and no cookie is set.

In your browser's developer tools, navigate to the "Network" tab. When you make a login request, you should see the Set-Cookie header in the response, and the JWT cookie should appear in the "Cookies" tab.

Conclusion

By leveraging environment variables and securely managing cookies, we've strengthened our API's configuration and security. This setup allows us to handle authentication tokens securely and flexibly adjust configuration settings. As we move forward, we'll continue to build upon this foundation, ensuring our application remains robust and secure. Stay tuned for more insights on how to effectively manage and utilize cookies within your Next.js applications!

Last updated