37

When saving data to Firebase database with a Firebase cloud function, I'd like to also write the IP address where the request comes from.

However, req.connection.remoteAddress always returns ::ffff:0.0.0.0. Is there a way to get the actual IP address of the client that makes the request?

tuomassalo
  • 8,717
  • 6
  • 48
  • 50
  • 1
    It looks as if you can no longer rely on x-forwarded-for. Try reading the fastly-client-ip. The other ip addresses seem to be google internal IP's – jqualls Jul 25 '18 at 07:50
  • 4 years and Google still no publishing any official documentation about it. It's crazy. – Broda Noel Jan 12 '22 at 05:31

10 Answers10

26

The clients IP is in request.ip.

Example:

export const pay = functions.https.onRequest((request, response) => {
  console.log(`My IP is ${request.ip}`);
});
Luke
  • 2,851
  • 1
  • 19
  • 17
  • 23
    When using Callable Functions (`functions.https.onCall((data, context) => {...})`), it's `context.rawRequest.ip` – sceee Jun 18 '20 at 07:59
  • 1
    Is it safe to use considering that its undocumented and could potentially be changed? – eozzy Sep 26 '20 at 23:56
  • 1
    @eozzy the documentation is at https://expressjs.com/en/guide/behind-proxies.html – Luke Oct 07 '20 at 07:09
  • 1
    I'm seeing undefined for context.rawRequest.ip - has this already been deprecated? – Stefan Feb 25 '21 at 16:49
  • 1
    @Stefan since I was probably the only one notified I thought I should test my example. The example still worked. I haven't looked at the onCall method but since request.ip still works so I would assume there is still a way to access the IP. Even if the IP address was deprecated in Firebase I would still think the ip would return a value. – Luke Feb 26 '21 at 00:57
  • 3
    Thanks Luke, ok my bad (but then again is it?) - I was using the emulator which is errrrm meant to emulate stuff. Turns out it contains only a few of the expected properties. For future readers, don't test thus locally. If you have an https.onCall then use `functions.logger.log(context.rawRequest.headers);` to see the wares. – Stefan Feb 26 '21 at 18:22
20

The IP address seems to be available in req.headers["x-forwarded-for"].
See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For

Note that if there are proxies in between the interim ip addresses are concatenated towards the end:

X-Forwarded-For: <client_ip>, <proxy_1 : actual-ip-as-seen-by-google> ...

Ujjwal Singh
  • 4,908
  • 4
  • 37
  • 54
tuomassalo
  • 8,717
  • 6
  • 48
  • 50
  • I see the ip address as doubles. GET ###.105.156.223, ###.105.156.223 – jwilleke Jul 23 '18 at 10:48
  • In this Gist https://gist.github.com/katowulf/6fffffb45ee5cbfbca6c3511e5d19528 they're using a similar method. However, there's a reply stating that this method is unreliable - have you found this to be reliable ? – user3836415 Aug 10 '18 at 14:57
  • this method doesn't *seem* to work anymore as it gives a new IP every request – imrok Sep 25 '18 at 10:50
  • 1
    This is what works, lift the first ip address – Ujjwal Singh Jan 17 '22 at 15:47
  • @UjjwalSingh lifting the first IP *can* work, but it *cannot* be trusted. For example the requester themselves could (maliciously?) add `X-Forwarded-For` to the outgoing request and, using this method, they can tell you whatever IP they want to, not their real IP. – Brett Apr 18 '23 at 20:29
19

If you are looking for the client ip thru firebase hosting you should use the header fastly-client-ip there will be the real client ip.

Fernando Cheong
  • 248
  • 2
  • 4
  • 3
    I actually thought this would be spam, but they changed it from `x-forwarded-for` to `fastly-client-ip`. Thanks for posting this update. – Robin Sep 18 '18 at 15:18
3

This worked for me:

const express = require('express');

const logIP = async (req : any, res : any, next : any) => {
    const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.headers['fastly-client-ip'];
}

const logIPApp = express();
logIPApp.use(logIP);

exports.reportIP = functions.https.onRequest(logIPApp);
hellmit100
  • 101
  • 1
  • 7
  • 1
    Why is logIP constant just creating a new constant within its scope? and doing nothing with it? Sorry, Am I missing something? – Sylvester Das Sep 23 '21 at 16:22
1

TL;DR

  • You can normally find the real client IP as the first entry in the X-Forwarded-For header, but you cannot trust it.

  • When using Cloud Run (or Cloud Functions gen 2), you can only trust the last entry (appended by the Cloud Run orchestrator itself).

  • Additionally, I can't find any way to trust the IP address if you are proxying Firebase Hosting.

If you need to trust the IP address

As explained by this answer https://stackoverflow.com/a/48032910/1513557, you can normally find the client's IP address by taking the first address from the X-Forwarded-For header, which is a comma-separated list of addresses, where each proxy along the way appends the client address it sees.

However the requester can (maliciously) add the X-Forwarded-For header themselves, and each proxy will append to it. In this way the requester can tell you whatever IP address they want.

If you are using Cloud Run (or Cloud Functions gen 2), then as far as I can tell the Cloud Run orchestrator always sits as a proxy in front of your container. This means you can trust the last entry in X-Forwarded-For as this is the client IP seen by Cloud Run itself. The last address is the only address you can trust. Any preceeding addresses in the list could have been provided by the requester or any untrusted proxy along the way.

If you are using Firebase Hosting

If you are proxying the the Cloud Run container (or Cloud Function) behind Firebase hosting, then the last address in the list is the Fastly CDN proxy, not the original client address. You might be tempted to use the second-to-last address (or the Fastly-Client-IP header), but you cannot trust this.

If the requester learns the direct address of your Cloud Run container (e.g. <your-service>.a.run.app) then they can make the request directly, and spoof either of those headers.

You may then be tempted to verify that the request is actually coming from Fastly/Firebase Hosting CDN by checking that the last IP address in the list is a trusted server (it appears that the request from the Fastly CDN to Cloud Run uses one of Google's IP ranges), but that doesn't rule out that the request could be coming from a malicious client hosted on Fastly or Google Cloud services.

This means you can't do it. You can only trust that last address, so you cannot proxy via Firebase Hosting. Unless the GCP/Firebase Hosting teams provide some "trusted header" in the future.

Brett
  • 298
  • 2
  • 16
0

This is what worked for me using Firebase Cloud Functions:

const clientIP = req.headers['x-appengine-user-ip'] || req.header['x-forwarded-for']

Note, that this doesn't work locally!

George Chalhoub
  • 14,968
  • 3
  • 38
  • 61
0

The previous answers are helpful, and I particularly appreciated the longer explanation from Brett.

If you come across this thread while specifically looking for how to get the client IP address in a 2nd generation onCall cloud function, the prior answers explain it conceptually but don't give a real example. The exact way to do it is:

exports.exampleFunction = onCall({cors: true}, (request) => {
  var index = request.rawRequest.rawHeaders.indexOf('x-forwarded-for');
  console.log(request.rawRequest.rawHeaders[index+1]);
});

In a 2nd generation onCall function, request.rawRequest.rawHeaders is an array. You look for the array value with x-forwarded-for, then the IP address is the next element in the array after this one.

Sharing this in case it is helpful to others.

And this feels like a clunky way to get it, so if there is a better method for 2nd generation functions, I'd love to know.

most200
  • 874
  • 1
  • 8
  • 9
0

To obtain the client IP address in a Firebase cloud function, I've created the following code:

function getIPFromRequest(request: any) {
    return (
          request.ip ||
          request.headers['fastly-client-ip'] ||
          request.headers['x-forwarded-for']?.split(',')[0].trim() ||
          request.connection.remoteAddress ||
          request.socket.remoteAddress ||
          ''
    );
}

In certain Node.js frameworks or middleware like Express.js, you can access the client's IP address using request.ip. And in principle, the request.ip is actually set by fetching the headers. But assuming request.ip was not set, we just fetch the headers ourselves. This is what getIPFromRequest does in simple terms.

Beyondo
  • 2,952
  • 1
  • 17
  • 42
0

The only method documented and thus supported by Firebase / GCloud Functions is the 'X-Forwarded-For' header:

Reference:

As of August 2023, the code now I use is ...

const header = ctx.rawRequest.header.bind(ctx.rawRequest)
const ip = (header('x-forwarded-for') || '').split(',')[0]
const ipCountry = header('x-appengine-country') || ''

Note that x-appengine-user-ip no longer works as it returns an IP address that is not the client's IP (looks like a server or proxy IP).

Tony O'Hagan
  • 21,638
  • 3
  • 67
  • 78
-1

If you are looking for ip address or headers in cloud callable function, then you can get information inside context object.

for ex:

    exports.testUser = async (data, context) => {
      console.log('------------------------::context::------------------');
      if(context.rawRequest && context.rawRequest.headers){
         console.log(context.rawRequest.headers);
      }
    }

In the headers you can get ip : header { 'x-appengine-user-ip' : 'xxip' } etc.

  • I've tried this in my auth function but there is no IP information in the headers. `exports.auth = functions.https.onCall(async ({ roomid, userid }, context => {...` – Stefan Feb 25 '21 at 16:48
  • Do you tried by `context.rawRequest.headers['x-appengine-user-ip']` this way? – Bhargav Suthar Feb 25 '21 at 17:49
  • Thanks, that gives me an undefined value but I just realised I was using the local emulator so this would most likely not work there. However the previous approach should still give an IP if present I think... I'll give this a try with a real firebase staging app. – Stefan Feb 26 '21 at 18:15
  • Yep, works if not running locally.... That emulator, shoot me now :-) – Stefan Feb 26 '21 at 18:23
  • @Stefan – Auth functions can now be set up to contain IP information in the 'EventContext' object if using Firebase Authentication with Identity Platform (https://firebase.google.com/docs/auth#identity-platform). For specific examples on how to setup & access the IP information see 'Getting user and context information' in https://firebase.google.com/docs/auth/extend-with-blocking-functions – lemonshark12 Jan 10 '23 at 14:28