Tutorial: How to Detect VPNs and Tor Users in Node.js Express

Published: (December 15, 2025 at 10:46 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Overview

If you run any kind of public API, SaaS, or forum, you already know the pain: bot traffic.
You ban a user for spamming, and seconds later they’re back with a new account because they toggled their VPN. You block an IP, and they switch to a Tor exit node.

In this tutorial, I’ll show you how to detect non‑residential IPs (VPNs, proxies, and hosting centers) in your Node.js application so you can block them—or at least challenge them with a CAPTCHA—before they touch your database.

The Goal

We want a middleware function in Express that looks like this:

app.use((req, res, next) => {
  if (isHighRisk(req.ip)) {
    return res.status(403).send("VPNs are not allowed.");
  }
  next();
});

Here is how to build it.

Method 1: The “Hard” Way (Self‑Hosted Lists)

Step 1: Get the Data

  • Tor Exit Nodes – The Tor project publishes a list of exit addresses.
  • Cloud Ranges – AWS and Google publish their IP ranges in massive JSON files.

You’ll need to download these files (e.g., tor-exit-nodes.txt, aws-ip-ranges.json) and keep them up to date.

Step 2: The Code

const fs = require('fs');
const ipRangeCheck = require('ip-range-check'); // npm install ip-range-check

// Load the massive lists into memory (careful with RAM!)
const torNodes = fs.readFileSync('tor-exit-nodes.txt', 'utf8')
  .split('\n')
  .filter(Boolean);
const awsRanges = JSON.parse(
  fs.readFileSync('aws-ip-ranges.json', 'utf8')
).prefixes.map(p => p.ip_prefix);

function isHighRisk(userIp) {
  // Check if IP is in the Tor list
  if (torNodes.includes(userIp)) return true;

  // Check if IP is in a Cloud Range (CPU intensive)
  if (ipRangeCheck(userIp, awsRanges)) return true;

  return false;
}

The Problem with Method 1

  • Stale data – VPN providers rotate IPs daily. Without hourly updates you’ll miss many attacks.
  • Memory hog – Loading millions of IPs into Node.js memory can crash your server.
  • False positives – Distinguishing a legitimate data‑center IP from a malicious VPN can be tricky.

Method 2: The “Easy” Way (Live API Lookup)

Step 1: Get a Free API Key

Grab a free key from the provider’s website (no credit card required).

Step 2: The Middleware

The API returns a trustScore (0‑100) along with flags for Tor and VPN.

const axios = require('axios');

async function checkRiskScore(req, res, next) {
  const userIp = req.ip;

  try {
    const response = await axios.get('https://candycorndb.com/api/public/ip-score', {
      params: { ip: userIp }
    });

    const { score, isTor, isVPN } = response.data;

    // BLOCK if it's a confirmed Tor node or very high risk
    if (isTor || score > 85) {
      return res.status(403).json({ error: 'Anonymizers not allowed.' });
    }

    // CHALLENGE if it's suspicious (e.g., DigitalOcean droplet)
    if (score >= 50) {
      // Insert CAPTCHA logic here…
      console.log(`Suspicious traffic from ${userIp}`);
    }

    next();
  } catch (err) {
    // Fail open: if the API is down, let the user in so you don’t block real people
    next();
  }
}

// Apply to your sensitive routes
app.post('/api/signup', checkRiskScore, (req, res) => {
  res.send("Account created!");
});

Why This Is Better

  • Just‑in‑time scanning – If the API hasn’t seen the IP before, it scans open ports and ISP data in < 500 ms, so you never get “unknown.”
  • No maintenance – No need to download daily CSV dumps.
  • Saves RAM – Your Node server handles logic, not massive IP storage.

Summary

Blocking bad IPs is an arms race. If you’re building a small hobby project, Method 1 is a fun learning exercise. For production apps, offloading risk detection to a dedicated API (Method 2) is usually cheaper than the time you’ll spend unbanning spam accounts.

Feel free to ask questions about IP‑filtering logic! 😅

Back to Blog

Related posts

Read more »

Create Your First MCP App

TL;DR MCP Apps bring interactive UIs to conversational agents and other MCP clients. This tutorial shows how to create a simple yet powerful app source code he...

Experimental Hono auth npm package

What I’m Building I’m creating an auth package that developers can drop into their app without writing the usual boilerplate login, register, JWT, email verifi...