Next.js Discord

Discord Forum

Redirect middleware route based on users IP

Unanswered
Kuchi posted this in #help-forum
Open in Discord
KuchiOP
Need some help debugging this issue I'm having with grabbing a users IP -> redirecting the user in middleware.

The api returns :
{"location":{"country":"CA","region":"Alberta"},"ip":"10.1.0.18","realIP":"10.1.0.18","forwardedIP":"10.1.0.18","testFlag":"this is ip : 10.1.0.18 forwardedFor value found : 10.1.0.18, 10.1.0.18:64069","albertaIP":true}
And the middleware users the country + region values in the location object to determine where to route to
///api/geolocation-api 
// finds users IP value and returns a location (country + region) for the middleware to use 
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "../../lib/session";

export default withIronSessionApiRoute(async (req, res) => {
  const apiKey = process.env.GEOLOCATION_API_KEY;

  console.log("req.headers :", req.headers);
  var ip = "";
  var testFlag = 0;
  let realIp = req.headers["x-real-ip"] ? req.headers["x-real-ip"] : "";
  var forwardedFor = req.headers["x-forwarded-for"];

  if (forwardedFor) {
    ip = forwardedFor.split(",").pop(); // Get the last IP address from the list
    ip = ip.trim(); // Remove any leading/trailing spaces

    if (ip.includes(":")) {
      ip = ip.split(":")[0]; // If there's a port, remove it
    }
    testFlag =
      "this is ip : " + ip + "   forwardedFor value found : " + forwardedFor;
  } else if (req.headers["x-real-ip"]) {
    ip = req.headers["x-real-ip"];
    testFlag = ip + "   real ip used value found";
  }

  if (!ip || ip === null) {
    // If IP address couldn't be determined, handle it accordingly
    testFlag = "value not found";
    res.status(500).json({ error: "Failed to determine IP address" });
    return;
  }
  const url = `https://geo.ipify.org/api/v2/country,city?apiKey=${apiKey}&ipAddress=${ip}`;
  console.log("url", url);
  try {
    if (ip.includes("10.1.0.")) {
      const location = {
        country: "CA",
        region: "Alberta",
      };

      var albertaIP;
      res.status(200).json({
        location,
        ip: ip,
        realIP: realIp,
        forwardedIP: forwardedFor.split(",")[0],
        testFlag: testFlag,
        albertaIP: true,
      });
    }

    const response = await fetch(url);
    if (response.ok) {
      const data = await response.json();
      console.log("API Response:", data);

      const location = {
        country: data?.location?.country,
        region: data?.location?.region,
      };
      
      var responseWasOk;
      res.status(200).json({
        location,
        ip: ip,
        realIP: realIp,
        forwardedIP: forwardedFor.split(",")[0],
        testFlag: testFlag,
        responseWasOk: true,
      });
    } else {
      throw new Error("Failed to fetch geolocation");
    }
  } catch (error) {
    console.error("Error fetching geolocation:", error);
    res.status(500).json({ error: "Failed to fetch geolocation" });
  }
}, sessionOptions);

18 Replies

KuchiOP
middleware.ts
import { NextResponse } from "next/server";
import { getIronSession } from "iron-session/edge";
import { sessionOptions } from "./lib/session";

export const middleware = async (req) => {

  const res = NextResponse.next();


  const urlAlberta = req.nextUrl.clone();


  //get current session if it exists
  const session = await getIronSession(req, res, sessionOptions);
  //grab current session + display current session data
  const { location } = session;

  // REDIRECT LOGIC HERE //
  //SESSION ALREADY EXISTS
  //if user lands on any page but they're from alberta, send them to the Alberta page version of the req.url

  if (
    session?.location?.location &&
    location?.location?.country === "CA" &&
    location?.location?.region === "Alberta" &&
    urlAlberta.pathname.indexOf("/AB") === -1
  ) {
    const locale = req.cookies.get("NEXT_LOCALE")?.value || "en";

    const albertaURL =
      locale === "en"
        ? urlAlberta.origin + "/" + "AB" + urlAlberta.pathname
        : urlAlberta.origin + "/" + locale + "/" + "AB" + urlAlberta.pathname;

    return NextResponse.redirect(new URL(albertaURL, req.url));
  }
  console.log("location", location);
  try {
    //NO SESSIONS
    //get user IP and set the browser session for user location
    console.log("NO SESSION FOUND - FETCHING FROM API")
    if (!session.location) {
      console.log("!Session.location")
      //   fetch user IP + local data and set it in a session cookie
      const url = process.env.PROD_URL + `api/geolocation-api`;
      console.log("url", url)
      const res_location = await fetch(url);
      const locationData = await res_location.json();
      session.location = locationData;
      console.log("locationData REGION : ", locationData.location)
      await session.save();

      if (
        locationData?.location?.country === "CA" &&
        locationData?.location?.region === "Alberta" &&
        urlAlberta.pathname.indexOf("/AB") === -1
      ) {
        const locale = req.cookies.get("NEXT_LOCALE")?.value || "en";

        const albertaURL =
          locale === "en"
            ? urlAlberta.origin + "/" + "AB" + urlAlberta.pathname
            : urlAlberta.origin +
              "/" +
              locale +
              "/" +
              "AB" +
              urlAlberta.pathname;

        return NextResponse.redirect(new URL(albertaURL, req.url));
      }
    }
  } catch (error) {
    console.error("Error fetching geolocation:", error);

    // Handle the error from the geolocation API
    // Redirect the user to the root site URL on API error
    const rootSiteUrl = process.env.PROD_URL; // Change this to your actual root site URL

    const redirectedUrl = new URL(rootSiteUrl);
    redirectedUrl.pathname = req.nextUrl.pathname; // Preserve the current path
    return NextResponse.redirect(redirectedUrl.toString());
  }
};

export const config = { matcher: "/((?!.*\\.).*)" };
I am not sure what is ur issue. Are you asking how to get an client IP or how to redirect an http request?
as for IP, u can simply grab it as follows.

export function middleware(request: NextRequest) {

    const clientIP = request.ip ?? "127.0.0.1";


but keep in mind that usually user traffics are via ISP server and/or VPN, you might not get exact location of web browsers.
another thing is, if you have a reverse-proxy in front of Vercel, x-forwarded-for is set to the reverse proxy IP address. or you need to contact Vercel to tell ur reverse-proxy a secured server.
KuchiOP
So sorry If I wasn't clear, I hadn't tried just getting req.ip, I'll give that a try and log it to see if its giving me anything
Its weird to me because I have IIS reverse proxy set up for my site and I'm passing the x-forwarded-for header through it to the app -
but my middleware is getting no headers,
-  // Get the client IP address from the 'x-forwarded-for' header
  console.log("req in middleware : ", req)  //empty {}
  const clientIP = req.headers['http_x_forwarded_for'];
  const realIpHeader = req.headers['http_x_real_ip'];
  // Log the client IP address
  console.log("Client IP:", clientIP); //null
  console.log("realIpHeader:", realIpHeader); //null
 


my api route called in my middleware is getting localhost IP loop back address 127.0.1
forwardedFor = req.headers["x-forwarded-for"]; //127.0.0.1

and my SSR logic is able to actually get the client ip.
export async function getServerSideProps(context) {
  const res = await fetch(process.env.WEB_API_HOME);
  const data = await res.json();
  const forwardIp = context.req.headers["x-forwarded-for"] || null; //10.0.1...
  const remoteIP = context.req.socket.remoteAddress || null; //null

  return {
    props: {
      data,
      forwardIp: forwardIp,
      remoteIP: remoteIP,
    },
  };
}
it looks okay

forwardedFor = req.headers["x-forwarded-for"]; //127.0.0.1
IP address is coming from IP layer, not HTTP(layer 7)
@tafutada777 IP address is coming from IP layer, not HTTP(layer 7)
KuchiOP
need to brush up on the layers i dont remember them from CS lol
its like networking layers
HTTP header, x-forwarded-for are easily modified by cURL or something by hacker. so you cannot trust them.
KuchiOP
Yea that makes sense, I might advise my team to forgo using the ip fetching then
so usually u see your reverse-proxy ip address in x-forwarded-for unless ur reverse-proxy is in the trusted server list.
The public IP address of the client that made the request. If you are trying to use Vercel behind a proxy, we currently overwrite the
X-Forwarded-For
header and do not forward external IPs. This restriction is in place to prevent IP spoofing. Please contact us if allowing Vercel to trust your X-Forwarded-For IP is a feature your Team needs (Enterprise only).
KuchiOP
I thought it might be okay since I'm not using vercel to do this, and on IIS in my project my web.config I put the server variables in it but it seems like I either did it wrong or it doesnt work as intended.
how about x-vercel-forwarded-for
plus, as for IIS, see
https://serverfault.com/questions/862951/iis-does-not-set-x-forwarded-host
or u can get a C# ASP.NET up and running and see if IIS forward x-forwarded-host correctly to compare it with Next.js