Next.js Discord

Discord Forum

Protected API routes for Firebase

Answered
Cape lion posted this in #help-forum
Open in Discord
Cape lionOP
Hello everyone,

I'm fairly new to the framework and have a few questions regarding API routes with the NextJS application.

It's fairly simple, a user submits an email to be notified of updates. That email gets stored in a firebase database. There is no sign in for this application so it is public. I've created my API route in the folder structure pages > api > consent > email

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {

  if (req.method === 'POST') {
    try{
      const dbReference = db.database().ref(`/marketing/awesome-sauce`)

      const result = res.json({
        message: "Email Successfully Saved"
      })

      const { email } = req.body

      await dbReference.update({
        email: email,
      })

      res.status(200).json({ result })
    } catch(error) {
      console.error(error)
      res.status(500).json({error: "Internal Server Error"})
    }

  } else {
    const result = res.json({
      error: "HTTP Method not Supported"
    })

    res.status(500).send({result})
  }
}


Pretty boiler plate code. I need to make sure that no one can do anything malicious if they know what the route is. I'm looking for a way to protect this route, maybe have it only allow the route to be called if it's coming from the application? I'm assuming there is a middleware solution but am having trouble finding documentation on doing something like this. Everything I find deals with a user authorization and sign in.
Answered by joulev
Impossible. There is no way to check for sure whether a request came from your frontend or from elsewhere. They are independent http requests after all and anyone can copy the full details of http requests they make.

Closest you can get is a captcha + rate limiting, if you want to keep this thing public.
View full answer

5 Replies

Impossible. There is no way to check for sure whether a request came from your frontend or from elsewhere. They are independent http requests after all and anyone can copy the full details of http requests they make.

Closest you can get is a captcha + rate limiting, if you want to keep this thing public.
Answer
French Angora
This refactored code below will ensure that only requests from allowed domains or your application's domain are processed, adding an extra layer of security to your API endpoint.

import type { NextApiRequest, NextApiResponse } from 'next';
import { parse } from 'url';

// Define allowed domains
const ALLOWED_DOMAINS = ['your-allowed-domain.com', 'localhost']; // Add your allowed domains here
const APP_DOMAIN = process.env.APP_DOMAIN || 'your-app-domain.com'; // Set your app's domain

function isAllowedOrigin(origin: string | null): boolean {
  if (!origin) return false;
  
  const hostname = parse(origin).hostname || '';
  return ALLOWED_DOMAINS.includes(hostname) || hostname === APP_DOMAIN;
}

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // Check origin
  const origin = req.headers.origin;
  if (!isAllowedOrigin(origin)) {
    return res.status(403).json({ error: "Forbidden: Invalid origin" });
  }

  if (req.method === 'POST') {
    try {
      const dbReference = db.database().ref(`/marketing/awesome-sauce`);
      const { email } = req.body;

      await dbReference.update({ email: email });

      res.status(200).json({ message: "Email Successfully Saved" });
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: "Internal Server Error" });
    }
  } else {
    res.status(405).json({ error: "HTTP Method not Supported" });
  }
}
and you can always just spin up postman, add your-app-domain.com to the origin header, and the entire isAllowedOrigin check falls apart.

blocking all requests except those coming from the frontend is impossible. the above code is certainly a mitigation against the weaker attackers, but it's false security and very easy to bypass.
Cape lionOP
Thank you for the responses. I was thinking of going the route of a CSRF token. I’ve been trying some packages out but unfortunately it depends on server props and I’m already using static props, which don’t play well together 🥺
Cape lionOP
Circling back here... I was aiming at using CSRF tokens but the current way the NextJS application I'm working on was built won't allow the use of server side props (the way the SSG and lazy loading are setup with static props).

So the end solution was going the route of reCaptcha. Thank @joulev for the direction and follow ups and @French Angora for the suggestion. reCaptcha was the winning solution here. Maybe I'll do the "allowed domain" approach too, security is redundancy 😅.