How do I get user IP address in Django?
Asked Answered
D

15

389

How do I get user's IP in Django?

I have a view like this:

# Create your views
from django.contrib.gis.utils import GeoIP
from django.template import  RequestContext
from django.shortcuts import render_to_response

def home(request):
  g = GeoIP()
  client_ip = request.META['REMOTE_ADDR']
  lat,long = g.lat_lon(client_ip)
  return render_to_response('home_page_tmp.html',locals())

But I get this error:

KeyError at /mypage/
    'REMOTE_ADDR'
    Request Method: GET
    Request URL:    http://mywebsite.example/mypage/
    Django Version: 1.2.4
    Exception Type: KeyError
    Exception Value:
    'REMOTE_ADDR'
    Exception Location: /mysite/homepage/views.py in home, line 9
    Python Executable:  /usr/bin/python
    Python Version: 2.6.6
    Python Path:    ['/mysite', '/usr/local/lib/python2.6/dist-packages/flup-1.0.2-py2.6.egg', '/usr/lib/python2.6', '/usr/lib/python2.6/plat-linux2', '/usr/lib/python2.6/lib-tk', '/usr/lib/python2.6/lib-old', '/usr/lib/python2.6/lib-dynload', '/usr/local/lib/python2.6/dist-packages', '/usr/lib/python2.6/dist-packages', '/usr/lib/pymodules/python2.6']
    Server time:    Sun, 2 Jan 2011 20:42:50 -0600
Drye answered 3/1, 2011 at 2:55 Comment(3)
Try dumping request.META.keys()Knowling
['HTTP_COOKIE', 'SCRIPT_NAME', 'REQUEST_METHOD', 'PATH_INFO', 'SERVER_PROTOCOL', 'QUERY_STRING', 'CONTENT_LENGTH', 'HTTP_ACCEPT_CHARSET', 'HTTP_USER_AGENT', 'HTTP_CONNECTION', 'SERVER_NAME', 'wsgi.url_scheme', 'SERVER_PORT', 'wsgi.input', 'HTTP_HOST', 'wsgi.multithread', 'HTTP_CACHE_CONTROL', 'HTTP_ACCEPT', 'wsgi.version', 'wsgi.run_once', 'wsgi.errors', 'wsgi.multiprocess', 'HTTP_ACCEPT_LANGUAGE', 'CONTENT_TYPE', 'CSRF_COOKIE', 'HTTP_ACCEPT_ENCODING']Drye
Thank you for this great question. My fastcgi was not passing the REMOTE_ADDR meta key. I added the line below in the nginx.conf and fixed the problem: fastcgi_param REMOTE_ADDR $remote_addr;Drye
E
585
def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

Make sure you have reverse proxy (if any) configured correctly (e.g. mod_rpaf installed for Apache).

Note: the above uses the first item in X-Forwarded-For, but you might want to use the last item (e.g., in the case of Heroku: Get client's real IP address on Heroku)

And then just pass the request as argument to it;

get_client_ip(request)

Django documentation for HttpRequest.META

Enmesh answered 3/1, 2011 at 4:8 Comment(22)
I'm using django + nginx + fastcgiDrye
Still, log the contents of request.META and tweak your server config if neither 'HTTP_X_FORWARDED_FOR' nor 'REMOTE_ADDR' key is there.Enmesh
How do I use the "get_client_ip" in my view? --Thank you!Drye
If I use: lat,long = g.lat_lon(get_client_ip) I'm having a casting issue. It's expecting a string.Drye
Call ip = get_client_ip(request) in your view function.Enmesh
Still no luck. I get: GeoIP query must be a string, not type NoneTypeDrye
I converted the ip into a string but when I pull the values for the "ip" and "lat" in the browser I see "None:. Here is the code: def home(request): g = GeoIP() ip = str(get_client_ip(request)) lat = g.lat_lon(ip) return render_to_response('home_page_tmp.html',locals())Drye
Figured it out. My fastcgi didn't pass the REMOTE_ADDR meta key. Thank you for your help.Drye
The real client IP address is not the first but the last in HTTP_X_FORWARDED_FOR (see wikipedia page)Dauphine
@Dauphine That's not correct. The format is typically X-Forwarded-For: client, proxy1, proxy2. So the first address is the client's.Referendum
This function is dangerous. With many setups a malicious user could easily cause this function to return any address they want (instead of their real one). See esd.io/blog/flask-apps-heroku-real-ip-spoofing.htmlEdgington
@yanchenko, Here i am gettng user internal ip address(192.xxx.x.xx), But how can i get the external ip(public ip) of a end userOsrock
I agree with Eli. None of existing answers here can perfectly solve multiple proxies and IP spoof problem. The answer in Eli's page is a better oneLaevorotatory
From the django docs "relying on REMOTE_ADDR or similar values is widely known to be a worst practice" (djangoproject.com/weblog/2009/jul/28/security/#secondary-issue)Archimage
Don't use this. If your proxies (eg nginx) aren't configured properly, this method will start returning 127.0.0.1 for everything and you won't notice until 2 weeks later. Use the django-ipware library as suggested by this answer.Fidelity
It always returns 127.0.0.1 no mater who opens the webpageThant
This is a great example of stack overflow's community wisdom failing - This answer may cause more problems than it solves potentially. There are circumstances where this works but there are also circumstances where it will break, which means it's not a good solution. When it does break, you're not going to know why and you may not be able to easily fix it. As in the answer below, use django-ipware and save yourself potential heartache.Sherris
I don't understand why people always want to check X-Forwarded-For conditionally. If you are sure you are behind a reverse proxy, you don't need the REMOTE_ADDR case. If you might be open to the public directly, everyone cna send an X-Forwarded-For header and pretend to be someone else. In both cases you won't want to use both of them.Station
if your concern may be sql injection coming from IPADDR headers.. you can clean the values using built-in: from django.utils.html import strip_tags, escape.Hambletonian
The answer in the link alleging the origin IP is always the last isn’t valid anymore. Check that question for more information.Wreckful
Im on a LAN and always getting the same IP, from every computerRaddled
This does not work on the Kubernetes cluster. It returns the local IP address of the service resource. I will test django-ipware and add another commentGladysglagolitic
S
266

You can use django-ipware which supports Python 2 & 3 and handles IPv4 & IPv6.

Install:

pip install django-ipware

Simple Usage:

# In a view or a middleware where the `request` object is available

from ipware import get_client_ip
ip, is_routable = get_client_ip(request)
if ip is None:
    # Unable to get the client's IP address
else:
    # We got the client's IP address
    if is_routable:
        # The client's IP address is publicly routable on the Internet
    else:
        # The client's IP address is private

# Order of precedence is (Public, Private, Loopback, None)

Advanced Usage:

  • Custom Header - Custom request header for ipware to look at:

    i, r = get_client_ip(request, request_header_order=['X_FORWARDED_FOR'])
    i, r = get_client_ip(request, request_header_order=['X_FORWARDED_FOR', 'REMOTE_ADDR'])
    
  • Proxy Count - Django server is behind a fixed number of proxies:

    i, r = get_client_ip(request, proxy_count=1)
    
  • Trusted Proxies - Django server is behind one or more known & trusted proxies:

    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.2.2'))
    
    # For multiple proxies, simply add them to the list
    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.2.2', '177.3.3.3'))
    
    # For proxies with fixed sub-domain and dynamic IP addresses, use partial pattern
    i, r = get_client_ip(request, proxy_trusted_ips=('177.2.', '177.3.'))
    

Note: read this notice.

Swart answered 24/4, 2013 at 23:31 Comment(10)
Take a look at its source code. It handles all the complications identified by the other answers here.Groundling
Thx @Heliodor -- Yep, I have made the module very simple for an average use-case and very flexible for a complex use-case. Minimally, you'd want to look at its github page before rolling your own.Swart
NOTE that django-ipware's settings are not secure by default! Anyone can pass one of the other variables and your site will log that IP. Always set IPWARE_META_PRECEDENCE_LIST to the variable that you use, or use an alternative like pypi.python.org/pypi/WsgiUnproxySelfsuggestion
@Selfsuggestion Could you elaborate a little? I can't find IPWARE_META_PRECEDENCE_LIST in the repo.Baxie
@Monolith:Sorry, I copied their README typoo, the setting is called IPWARE_META_PRECEDENCE_ORDER. However, better use WsgiUnproxy as I mentioned before.Selfsuggestion
It is actually called IPWARE_META_PRECEDENCE_ORDER and it is in ipware/defaults.py. You can set your own via the settings.py file.Swart
we now use get_real_ip instead of get_client_ipEalasaid
@Ealasaid Please note that as of 2.0.0, you should use get_client_ip(). get_real_ip is deprecated and will be removed in 3.0.Swart
@un33k Thank you for the information. We are still on django-ipware==1.1.5 so get_client_ip does not exist yet.Ealasaid
This is so old.. is it still good or is there a better one ?Charlatan
A
95

Alexander's answer is great, but lacks the handling of proxies that sometimes return multiple IP's in the HTTP_X_FORWARDED_FOR header.

The real IP is usually at the end of the list, as explained here: http://en.wikipedia.org/wiki/X-Forwarded-For

The solution is a simple modification of Alexander's code:

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[-1].strip()
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip
Assibilate answered 12/5, 2011 at 9:38 Comment(7)
Is it the IP at the end of the list? Reading that wikipedia link, it says "the left-most being the farthest downstream client", which sounds like the first value is the original client. Or does Django reverse it?Anetteaneurin
Yup, the ip is at the beginning of the list. This here is wrong.Bedouin
Actually, if the user is behind a proxy you would get the user's internal IP address, i.e. a RFC 1918 address. In most cases, that's not very desirable at all. This solution focuses on getting the external IP address of the client (the proxy address), which is the right-most address.Fibril
Thank you. Usually when I request keys from request.META I include a default value since headers are often mising: request.META.get('REMOTE_ADDR', None)Kai
@CarlG your code is more transparent, but the get method is inherited from django.utils.datastructures.MultiValueDict and the default value is None. But it definitely makes sense to include a default value if you actually wanted it to be something other than None.Fibril
@Assibilate it depends on whether the proxy is normal user proxy or a reverse proxy. Neither is reliable to identify the IP address of client's public/external IP. It heavily depends on the context.Approval
Unless you're scrubbing X-Forwarded-For when requests hit your first server, then the first value in that list is user supplied. A malicious user could easily spoof any IP address they want. The address you want is the first IP before any of your servers, not necessarily the first in the list.Edgington
G
30

No More confusion In the recent versions of Django it is mentioned clearly that the Ip address of the client is available at

request.META.get("REMOTE_ADDR")

for more info check the Django Docs

Gambia answered 20/2, 2020 at 10:53 Comment(1)
This gives blank value when the application is running behind a reverse proxy server(like Nginx). You will need X_FORWARDED_FORCq
R
19

I would like to suggest an improvement to yanchenko's answer.

Instead of taking the first ip in the X_FORWARDED_FOR list, I take the first one which in not a known internal ip, as some routers don't respect the protocol, and you can see internal ips as the first value of the list.

PRIVATE_IPS_PREFIX = ('10.', '172.', '192.', )

def get_client_ip(request):
    """get the client ip from the request
    """
    remote_address = request.META.get('REMOTE_ADDR')
    # set the default value of the ip to be the REMOTE_ADDR if available
    # else None
    ip = remote_address
    # try to get the first non-proxy ip (not a private ip) from the
    # HTTP_X_FORWARDED_FOR
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        proxies = x_forwarded_for.split(',')
        # remove the private ips from the beginning
        while (len(proxies) > 0 and
                proxies[0].startswith(PRIVATE_IPS_PREFIX)):
            proxies.pop(0)
        # take the first ip which is not a private one (of a proxy)
        if len(proxies) > 0:
            ip = proxies[0]

    return ip

I hope this helps fellow Googlers who have the same problem.

Rye answered 11/2, 2013 at 8:47 Comment(5)
This code does not check that the ip from REMOTE_ADDR is private before checking the HTTP_X_FORWARDED_FOR field, as it probably should (also, '127.0.0.1' or '127.' should probably be in PRIVATE_IPS_PREFIX, together with IPv6 equivalents.Footed
Technically, those prefixes (172, 192) do not necessarily mean private addresses.Gregale
The address ranges assigned for private networks are: 172.16.0.0–172.31.255.255 (16 “class B” networks), 192.168.0.0–192.168.255.255 (1 “class B” network) and 10.0.0.0–10.255.255.255 (1 “class A” or 256 “class B” networks).Schecter
is_valid_ip not definedHamlani
I like this approach, but I think the implementation is a bit dangerous. For instance, most IP addresses under the 10. prefix are public IPs. T-Mobile owns 172.32.0.0 as an example.Kellam
S
13

here is a short one liner to accomplish this:

request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()
Spine answered 16/9, 2017 at 18:16 Comment(3)
If both of them return None then you would get an error.Packer
It really works, it returns your current public ip address, thanks @masterbase, you made my day.Alphonsa
@GouravChawla some pretty simple conditionally logic would prevent this from erroring remote_ip = request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', '')) if remote_ip = remote_ip.split(',')[0] if remote_ip else NoneArica
G
8

In my case none of above works, so I have to check uwsgi + django source code and pass static param in nginx and see why/how, and below is what I have found.

Env info:
python version: 2.7.5
Django version: (1, 6, 6, 'final', 0)
nginx version: nginx/1.6.0
uwsgi: 2.0.7

Env setting info:
nginx as reverse proxy listening at port 80 uwsgi as upstream unix socket, will response to the request eventually

Django config info:

USE_X_FORWARDED_HOST = True # with or without this line does not matter

nginx config:

uwsgi_param      X-Real-IP              $remote_addr;
// uwsgi_param   X-Forwarded-For        $proxy_add_x_forwarded_for;
// uwsgi_param   HTTP_X_FORWARDED_FOR   $proxy_add_x_forwarded_for;

// hardcode for testing
uwsgi_param      X-Forwarded-For        "10.10.10.10";
uwsgi_param      HTTP_X_FORWARDED_FOR   "20.20.20.20";

getting all the params in django app:

X-Forwarded-For :       10.10.10.10
HTTP_X_FORWARDED_FOR :  20.20.20.20

Conclusion:

So basically, you have to specify exactly the same field/param name in nginx, and use request.META[field/param] in django app.

And now you can decide whether to add a middleware (interceptor) or just parse HTTP_X_FORWARDED_FOR in certain views.

Glyconeogenesis answered 17/11, 2014 at 3:47 Comment(0)
U
7

The simpliest solution (in case you are using fastcgi+nignx) is what itgorilla commented:

Thank you for this great question. My fastcgi was not passing the REMOTE_ADDR meta key. I added the line below in the nginx.conf and fixed the problem: fastcgi_param REMOTE_ADDR $remote_addr; – itgorilla

Ps: I added this answer just to make his solution more visible.

Uni answered 22/2, 2011 at 22:17 Comment(1)
What's a comparable solution for nginx (reverse proxy) and gunicorn? proxy_set_header REMOTE_ADDR $remote_addr; doesn't alleviate the problem when added to nginx.conf.Tressietressure
S
4

The reason the functionality was removed from Django originally was that the header cannot ultimately be trusted. The reason is that it is easy to spoof. For example the recommended way to configure an Nginx reverse proxy is to:

add_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header X-Real-Ip       $remote_addr;

When you do:

curl -H 'X-Forwarded-For: 8.8.8.8, 192.168.1.2' http://192.168.1.3/

Your Nginx in myhost.example will send onwards:

X-Forwarded-For: 8.8.8.8, 192.168.1.2, 192.168.1.3

The X-Real-IP will be the IP of the first previous proxy if you follow the instructions blindly.

In case trusting who your users are is an issue, you could try something like django-xff: https://pypi.python.org/pypi/django-xff/

Scarborough answered 16/11, 2015 at 7:17 Comment(0)
M
3

I was also missing proxy in above answer. I used get_ip_address_from_request from django_easy_timezones.

from easy_timezones.utils import get_ip_address_from_request, is_valid_ip, is_local_ip
ip = get_ip_address_from_request(request)
try:
    if is_valid_ip(ip):
        geoip_record = IpRange.objects.by_ip(ip)
except IpRange.DoesNotExist:
    return None

And here is method get_ip_address_from_request, IPv4 and IPv6 ready:

def get_ip_address_from_request(request):
    """ Makes the best attempt to get the client's real IP or return the loopback """
    PRIVATE_IPS_PREFIX = ('10.', '172.', '192.', '127.')
    ip_address = ''
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
    if x_forwarded_for and ',' not in x_forwarded_for:
        if not x_forwarded_for.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_forwarded_for):
            ip_address = x_forwarded_for.strip()
    else:
        ips = [ip.strip() for ip in x_forwarded_for.split(',')]
        for ip in ips:
            if ip.startswith(PRIVATE_IPS_PREFIX):
                continue
            elif not is_valid_ip(ip):
                continue
            else:
                ip_address = ip
                break
    if not ip_address:
        x_real_ip = request.META.get('HTTP_X_REAL_IP', '')
        if x_real_ip:
            if not x_real_ip.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_real_ip):
                ip_address = x_real_ip.strip()
    if not ip_address:
        remote_addr = request.META.get('REMOTE_ADDR', '')
        if remote_addr:
            if not remote_addr.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(remote_addr):
                ip_address = remote_addr.strip()
    if not ip_address:
        ip_address = '127.0.0.1'
    return ip_address
Maurene answered 31/1, 2016 at 0:22 Comment(0)
R
1

In django.VERSION (2, 1, 1, 'final', 0) request handler

sock=request._stream.stream.raw._sock
#<socket.socket fd=1236, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.111', 8000), raddr=('192.168.1.111', 64725)>
client_ip,port=sock.getpeername()

if you call above code twice,you may got

AttributeError("'_io.BytesIO' object has no attribute 'stream'",)

AttributeError("'LimitedStream' object has no attribute 'raw'")

Ramires answered 17/7, 2019 at 3:48 Comment(1)
Doesn't work in Django 4+Flat
K
1

Simply add

{{ request.META.REMOTE_ADDR }}

In Django-Template where you want the user to see their IP address. That is if you are not interested in saving this to the DB.

Kamacite answered 28/3, 2022 at 8:8 Comment(0)
P
1

Get the ip address with this function:

def get_ip_address(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

after that you can get the user location data and other info from that web app http://www.iplocinfo.com/:

import requests
def get_ip_data(request):
    ip_address = get_ip_address(request)
    api_key = "your api key"
    endPoint = f'https://www.iplocinfo.com/api/v1/{ip_address}?apiKey={api_key}'
    data = requests.get(endPoint)
    return data.json()
Paillette answered 29/1, 2023 at 14:43 Comment(0)
K
1

In the folder where manage.py is located I created a file named getIP with the folowing code:

import socket


def getip():
    hostname = socket.gethostname()
    ipddr = socket.gethostbyname(hostname)
    print("HOSTNAME:"+hostname)
    print("IP:"+ipddr)
    return f'{ipddr}'

in manage.py I did some modifications as following (Python 3.9 and Django 4.2.7):

#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys

from getIP import getip
from django.core.management.commands.runserver import Command as Crs


def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyApp.settings')
    try:
        from django.core.management import execute_from_command_line

        Crs.default_addr = getip()
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

in windows prompt command the output is:

(venv) C:\MyProject\MyApp>python manage.py runserver 
HOSTNAME:DESKTOP-xxxxxx
IP:192.168.XX.XX
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 08, 2023 - 10:26:31
Django version 4.2.7, using settings 'MyApp.settings'
Starting development server at http://192.168.XX.XX:8000/
Quit the server with CTRL-BREAK.
Knell answered 8/12, 2023 at 13:29 Comment(1)
This doesn't answer the question of how to get a user's IP, you are just printing the server IPNogood
M
0

After getting ip address you need to find location

# pip install geocoder

import geocoder

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
        ip_location = geocoder.ip(f"{ip}")
        ip_location = geocoder.ip("me")
        print(ip_location.city)
        # you can get city such as "New York"
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip
Meteoric answered 21/9, 2022 at 14:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.