Getting a valid user IP address when behind a reverse proxy

If your website is behind a reverse proxy (Nginx, Apache2, HA Proxy, pound, squid, …), you will find that the built in Session.RemoteAddress (or request.RemoteAddress) will give you the IP address of your reverse proxy, and not the client making the request. This function will try to help with that.

This will NOT work if you are using TCP or NAT port forwards.

Var RetVal As String = Session.RemoteAddress

// IMPORTANT NOTE: This will only work where a reverse HTTP(S) proxy is being used.  If your solution is based on: 
//                               * A TCP proxy, there will be no header, and the RemoteAddress will ALWAYS point to the TCP proxy server.
//                               * Firewall port forwarding, there will also be NO header, but the RemoteAddress MAY point to a valid IP (or it may not).

// - - - - - - - - - - START LOOKING FOR REVERSE PROXY ADDRESS
// FrontEndProxies should be a comprehensive list of all reverse proxies.  
// If an IP address is in this list, we need to look for another IP.  If not in this list, we assume that it's a valid IP address.
// If we will only ever be accessed via a reverse proxy, we can safely ignore this first section.
// Note spaces at the beginning and the end.  This is to help find a full match and not a partial match.
Var FrontEndProxies As String = " 127.0.0.1 x.x.x.x " 

If FrontEndProxies.IndexOf(" " + RetVal + " ") = -1  Then Return RetVal // Return address if it's not in a list of possible frontend proxies

// - - - - - - - - - - STOP LOOKING FOR REVERSE PROXY ADDRESS

//NOTE: Since you presumably set up the reverse proxy yourself, or had someone do it for you, or are using an existing service,
//            you should know which header is being used.  This is here for you to find out in case you do not already know.  Once you
//            know what is being used, comment out the rest.

d("Session.RemoteAddress appears to be a reverse proxy.  Looking for real IP...")

RetVal  = Session.Header("X-Real-IP")

if RetVal <> "" Then 
  d("Using X-Real-IP")
  Return RetVal
end if

RetVal = Session.Header("X-Forwarded-For").NthField(", ", 1)

if RetVal <> "" Then 
  d("Using X-Forwarded-For")
  Return RetVal
end if

RetVal = Session.RemoteAddress
d("Using Session.RemoteAddress after all.  Are you using a TCP proxy instead of a web proxy?")

Return RetVal

// Parts of this solution come from Tobias Bussman's code posted here: https://forum.xojo.com/t/reverse-proxy-ip-and-ssl/19096/3

One last note. If you are NOT using a reverse proxy, you really should consider doing so. While not required in some instances, in most instances a reverse proxy offers many benefits, including improved security and reduced server load.

If you configure your reverse proxy correctly this is not an issue.

Tim, I’m sorry but I have to disagree. The way the connection works is:

CLIENT BROWSER ==> REVERSE PROXY ==> XOJO WEB APP

More specifically,

CLIENT IP  ==> REVERSE PROXY IP ==> WEB APP SERVER IP

The TCP connection to the XOJO WEB APP will always be coming from the REVERSE PROXY’s IP (unless the CLIENT somehow bypasses the proxy).

A properly configured reverse proxy will pass a header to the web app that contains the client’s IP address (usually in the X-Forwarded-For header), but there is no reasonable or safe way for the proxy to connect from the client’s IP address.

It’s possible that you mean that a properly configured reverse proxy will send a header that the Xojo web app recognises and inserts into the Session.RemoteAddress field, but my (certainly very limited) testing has shown that not to be the case thus far, and it’s not mentioned in the (sparse) documentation describing Session.RemoteAddress.

If you still feel I am mistaken, perhaps you could provide an explanation or link to documentation that would explain further what you mean.

Xojo reads this header and puts the client IP address in the RemoteAddress property.

I know this because there are no special workarounds in this demo: https://ip.xojo.dev/
Source project: ip.xojo_xml_project

Edit: Updated project so that it will open in the current release version (2020r1)

Can you confirm that it is the X-Forwarded-For header?

If so, then I guess I’m going to have to figure out why it’s not working for me.

I don’t suppose you have any documentation that goes beyond the RemoteAddress wiki page?

Additionally, do you know how to get the IP address the connection is coming from (the remote proxy)?

Jason

Yes. I just configured nginx to pass a bogus value and the it came through (not on the instance above).

If you look at the feedback ticket from the thread that you linked, it’s marked as a duplicate of “23891 - Add support for reverse proxies to WE apps” which was marked “Implemented & Verified” in Xojo 2016r2.

I’m building an app that helps simplify deploying Xojo Web apps, it might be of some help :slight_smile: Public Testing Lifeboat - Deploy to Lightsail or Digital Ocean - #13 by Tim_Parnell

I played around, but wasn’t able to get access to the internal address. You might be able to configure the proxy to pass it in your own custom header, but I did not look into that idea. WebApplication has a Port property, so if you’re configuring the reverse proxy yourself you could infer the internal address. I don’t have a better answer for this one because I’ve never needed it myself.

The custom header is a good idea.

My bigger problem is that my app now segfaults when I try to run it (on Linux x64). Windows seems to run just fine.

What’s annoying is that I’ve done so much since I ran the first proof-of-concept build that I have no idea where to begin looking.

Aah, well.

Linux requires installing a couple of libraries that aren’t included in every flavor / install. Have you installed those libraries? You’ll need libsoup and libunwind, both of which can be yum installed on CentOS 7.

I actually didn’t see the feedback ticket, I was looking for a solution to my (perceived) problem. Having dealt with this in various other programming languages I assumed what I was seeing was normal. Guess not :wink:

I have a couple of VPS’ I use for this. We have a new privacy law in South Africa that requires we keep unencrypted client data in the country. While I don’t use AWS or Digital Ocean, I was unhappy at having to move a few VPS’ away from Linode…

I’ll look at downloading the app when I get a spare moment next week.

Using Ubuntu, but both are already installed (was working earlier today).

I’ll continue testing tomorrow. I’m trying to do the right thing and go to bed now…

The problem is the SQLite database - I’m guessing the app doesn’t know where to find the database, and I’m not sure how to tell it where to look.

I added the database via Insert/Database/Select SQLite database.

The resulting project object has no UI setting or code option to change the DatabaseFile location.

I should have been in bed long ago, so I’ll continue looking tomorrow (for real this time)

So the problem was on my side, not the web app. :\

I’ve flagged the post for deletion.

@Tim_Parnell, thanks for the heads up and the help.