Secure Mapbox Tiles: HTTP-Only Cookies Guide

by Henrik Larsen 45 views

Hey guys! Ever found yourself in a situation where you need to load raster tiles in your Mapbox GL JS project but also want to ensure that these tiles are served securely, especially when they reside outside your document root? It's a common scenario, and today, we're diving deep into how you can achieve this by leveraging HTTP-only cookies. We'll break down the problem, explore a practical solution, and provide you with a comprehensive guide to implement this in your own projects.

Understanding the Challenge

When dealing with mapping applications, especially those that require custom tiles, security is paramount. Imagine you have a set of raster tiles stored outside your web server's document root. These tiles are meant to be accessed only by authorized users. To enforce this, you've set up a controller that serves the tiles based on the standard x/y/z schema—a common way to organize map tiles. So, how do you ensure that only authenticated users can access these tiles?

The traditional approach might involve passing authentication tokens in the URL or using headers. However, these methods can be vulnerable to attacks like URL sniffing or Cross-Site Scripting (XSS). A more secure method is to use HTTP-only cookies. These cookies are designed to prevent client-side scripts from accessing them, adding an extra layer of security. This means that even if an attacker manages to inject malicious JavaScript into your page, they won't be able to read the cookie and use it to access your tiles.

Why HTTP-Only Cookies?

  • Enhanced Security: HTTP-only cookies cannot be accessed via JavaScript, mitigating XSS attacks.
  • Automatic Inclusion in Requests: Browsers automatically include HTTP-only cookies in HTTP requests to the server, simplifying the authentication process.
  • Stateless Authentication: Cookies allow you to maintain user sessions without needing to pass tokens in every request, making your application more efficient.

Setting Up Your Tile Server

Before we dive into the Mapbox GL JS code, let's quickly discuss how to set up your tile server to use HTTP-only cookies. Your server needs to authenticate users and, upon successful authentication, set an HTTP-only cookie. This cookie will then be sent with every subsequent request for tiles.

Here’s a simplified overview of the process:

  1. User Authentication: Implement a login mechanism that verifies user credentials.
  2. Cookie Creation: Upon successful login, create a session cookie and set the HttpOnly flag. For example, in Node.js with Express, you might use the cookie or cookie-parser middleware to set the cookie.
  3. Tile Serving: When a request for a tile comes in, check for the presence and validity of the cookie. If the cookie is valid, serve the tile; otherwise, return an unauthorized response.

Example (Node.js with Express)

const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();

app.use(cookieParser());

app.post('/login', (req, res) => {
  // Authentication logic here
  if (userIsAuthenticated) {
    res.cookie('sessionId', 'uniqueSessionId', { httpOnly: true });
    res.send({ message: 'Login successful' });
  } else {
    res.status(401).send({ message: 'Authentication failed' });
  }
});

app.get('/tiles/:z/:x/:y.png', (req, res) => {
  if (req.cookies.sessionId) {
    // Logic to serve the tile
    res.sendFile(path.join(__dirname, 'tiles', req.params.z, req.params.x, req.params.y + '.png'));
  } else {
    res.status(403).send('Unauthorized');
  }
});

This is a basic example, and you'll need to adapt it to your specific technology stack and authentication needs. Remember to use secure practices like HTTPS to protect the cookie during transmission.

Integrating with Mapbox GL JS

Now, let's get to the core of the issue: loading raster tiles with HTTP-only cookies in Mapbox GL JS. The key here is to ensure that the cookies are sent with the tile requests. Mapbox GL JS doesn't directly expose an API to manipulate request headers or cookies for tile requests. However, we can work around this limitation by using a proxy.

The Proxy Approach

The idea is to set up a proxy endpoint on your server that forwards the tile requests, including the cookies, to your tile server. This proxy acts as an intermediary, ensuring that the HTTP-only cookies are included in the request.

Here’s a step-by-step guide:

  1. Create a Proxy Endpoint: Set up an endpoint on your server (e.g., /tile-proxy) that will forward requests to your tile server.
  2. Forward the Request: In this endpoint, extract the tile coordinates (x, y, z) from the request and forward the request to your tile server, making sure to include any cookies received from the client.
  3. Return the Tile: Once you receive the tile from your tile server, send it back to the client.

Example (Node.js with Express Proxy)

app.get('/tile-proxy/:z/:x/:y.png', (req, res) => {
  const { z, x, y } = req.params;
  const tileUrl = `http://your-tile-server.com/tiles/${z}/${x}/${y}.png`;

  // Forward the request with cookies
  request({
    url: tileUrl,
    method: 'GET',
    headers: {
      Cookie: req.headers.cookie, // Forward cookies
    },
    encoding: null, // Important for binary data (images)
  }, (error, response, body) => {
    if (error) {
      console.error('Proxy error:', error);
      return res.status(500).send('Proxy error');
    }

    res.set({
      'Content-Type': response.headers['content-type'],
      // Add other headers as needed
    });
    res.status(response.statusCode).send(body);
  });
});

In this example, we use the request library (you may need to install it via npm install request) to forward the request. The key part is forwarding the Cookie header from the original request to the tile server. Also, note the encoding: null option, which is crucial for handling binary data like images.

Configuring Mapbox GL JS

Now that you have your proxy endpoint set up, you need to configure Mapbox GL JS to use it. When adding a raster source, specify the URL to your proxy endpoint instead of the direct tile URL.

map.on('load', () => {
  map.addSource('secure-tiles', {
    type: 'raster',
    tiles: [
      '/tile-proxy/{z}/{x}/{y}.png', // Use the proxy endpoint
    ],
    tileSize: 256,
  });

  map.addLayer({
    id: 'secure-tiles-layer',
    type: 'raster',
    source: 'secure-tiles',
    minzoom: 0,
    maxzoom: 22,
  });
});

By pointing the tiles array to your proxy endpoint, Mapbox GL JS will now request tiles through your proxy, ensuring that the HTTP-only cookies are included in the requests. This setup allows your server to authenticate the requests and serve the tiles only to authorized users.

Best Practices and Considerations

  • HTTPS: Always use HTTPS to encrypt the communication between the client, your server, and the tile server. This is crucial for protecting the cookie and the data being transmitted.
  • Cookie Security: Set appropriate cookie attributes such as Secure (to ensure the cookie is only sent over HTTPS) and SameSite (to mitigate CSRF attacks).
  • Caching: Implement caching on your server and in Mapbox GL JS to reduce the load on your tile server and improve performance. You can use HTTP caching headers or Mapbox GL JS's built-in caching mechanisms.
  • Monitoring and Logging: Monitor your proxy endpoint for any errors or suspicious activity. Logging requests can help you troubleshoot issues and detect potential attacks.
  • Scalability: If you anticipate a high volume of tile requests, consider using a more robust proxy solution like Nginx or a dedicated API gateway.

Conclusion

Securing your raster tiles with HTTP-only cookies in Mapbox GL JS might seem complex at first, but with the proxy approach, it becomes manageable. By setting up a proxy endpoint on your server, you can ensure that HTTP-only cookies are included in tile requests, adding a strong layer of security to your mapping application. Remember to follow best practices for security and performance to create a robust and efficient system.

I hope this guide has been helpful! If you have any questions or run into any issues, feel free to drop a comment below. Happy mapping!