Redirect middleware route based on users IP
Unanswered
Kuchi posted this in #help-forum
KuchiOP
Need some help debugging this issue I'm having with grabbing a users IP -> redirecting the user in middleware.
The api returns :
And the middleware users the country + region values in the location object to determine where to route to
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.
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.
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,
my api route called in my middleware is getting localhost IP loop back address 127.0.1
and my SSR logic is able to actually get the client ip.
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.1and 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.1IP 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
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