A small Flask extension that checks whether a visitor's IP address is a known proxy / VPN / Tor / data-center address using the IP2Proxy Python library, and stores the result in the Flask session — so any other view or template in the app can reuse it without repeating the lookup.
From PyPI, once published:
pip install flask-ip2proxyFrom this source tree (editable install, for development):
pip install -e ".[dev]"IP2Proxy and Flask install automatically either way.
- On the first request in a session, a
before_requesthook checks the visitor's IP against your local IP2Proxy.BINdatabase and stores the result undersession["ip2proxy"]. - On later requests, if the visitor's IP hasn't changed, the cached value is reused — no repeat database queries.
- Any view or Jinja template can read
session["ip2proxy"]directly, or import thecurrent_proxy_infoproxy for convenience.
-
Get a database file. This extension requires a IP2Location database BIN file to work. You may obtain a free LITE database or purchase a commercial database as below:
- Free, self-updating "LITE" editions: https://www.ip2location.com/database/lite
- Full commercial editions with ISP/domain/etc: https://www.ip2location.com/database/ip2proxy
Save the
.BINfile somewhere your app can read it. -
Wire it up
from flask import Flask from flask_ip2proxy import IP2ProxyFlask, current_proxy_info app = Flask(__name__) app.config["SECRET_KEY"] = "change-me" app.config["IP2PROXY_DB_PATH"] = "/path/to/IP2PROXY-LITE-PX1.BIN" ip2proxy = IP2ProxyFlask(app) @app.route("/") def index(): if current_proxy_info and current_proxy_info["is_proxy_flagged"]: return "You appear to be using a proxy or VPN." return "Hello!"
Or with the application-factory pattern:
ip2proxy = IP2ProxyFlask() def create_app(): app = Flask(__name__) app.config["IP2PROXY_DB_PATH"] = "..." ip2proxy.init_app(app) return app
-
Try the full demo:
pip install -e . # edit examples/example_app.py to point at your .BIN file python examples/example_app.py
Or the quickstart-style version:
python examples/hello_app.py # then visit http://127.0.0.1:5000/hello/ or /hello/YourName
session["ip2proxy"] is a plain dict (or None if the lookup failed, was
skipped, or the IP couldn't be resolved). Two fields are always present
when a lookup succeeds:
| Key | Meaning |
|---|---|
is_proxy |
Raw code from the library: -1 unknown/error (never stored — see below), 0 not a proxy, 1 a proxy, 2 a data-center/search-engine proxy. |
is_proxy_flagged |
Convenience boolean: True if is_proxy is 1 or 2. This is the field most apps actually want. |
Everything else (proxy_type, country_short, country_long, region,
city, isp, domain, usage_type, asn, as_name, last_seen,
threat, provider, fraud_score, plus ip) is included automatically
whenever your .BIN edition supports it — see "Automatic column
detection" below.
A result with is_proxy == -1 (IP2Proxy's way of saying "couldn't
determine this") is treated as a failed lookup and stored as None
rather than a dict full of placeholder text, matching how a private/local
IP or any other lookup failure is represented.
from flask import session
session["ip2proxy"] # plain dict, or Nonefrom flask_ip2proxy import current_proxy_info
current_proxy_info["is_proxy_flagged"] # LocalProxy, behaves like the dict{{ session.ip2proxy.is_proxy_flagged }}That last one needs no setup — Flask injects session into every
template automatically, so session.ip2proxy.whatever just works. See
the flask-ip2location README's "Reading the data elsewhere" section for
a fuller walkthrough of this (including a worked example extending
Flask's own quickstart hello.html, mirrored here in examples/).
You don't need to know which .BIN edition you're using — the extension
detects available columns from the query result itself, the same idea as
Flask-IP2Location but adapted to how IP2Proxy actually reports it: rather
than only setting attributes for supported fields, IP2Proxy's get_all()
always returns every possible key, filling in the literal text
"NOT SUPPORTED" for anything your database edition doesn't cover. The
extension drops exactly those placeholder entries, so:
- A
PX2LITE database gives youis_proxy,is_proxy_flagged,proxy_type,country_short,country_long,ip— nothing else, because that's allPX2contains. - A higher
PXtier or commercial edition additionally gives youisp,domain,asn,fraud_score, etc., automatically — nothing to configure, and nothing to update if you later upgrade your database.
If you want to keep only a subset regardless of what's available, set
IP2PROXY_FIELDS to an explicit list and it's applied as a filter on top
of auto-detection.
| Config key | Default | Description |
|---|---|---|
IP2PROXY_DB_PATH |
required | Path to the .BIN database file. |
IP2PROXY_SESSION_KEY |
"ip2proxy" |
Session dict key the result is stored under. |
IP2PROXY_FIELDS |
None (auto) |
Optional allow-list restricting which auto-detected fields are kept, e.g. ["is_proxy_flagged", "proxy_type"]. |
IP2PROXY_AUTO_LOOKUP |
True |
Automatically look up on every request via before_request. Set False and call ip2proxy.refresh() yourself to trigger it manually instead (e.g. only at login or checkout). |
IP2PROXY_SKIP_PRIVATE_IPS |
True |
Skip the database query entirely for loopback/private/reserved IPs (e.g. 127.0.0.1 during local dev), storing None instead of a meaningless result. |
IP2PROXY_TRUST_PROXY_HEADER |
False |
Trust the client-supplied X-Forwarded-For header for the visitor IP. See the caveat below — it's especially relevant here, since a visitor trying to evade proxy detection has every reason to spoof this exact header. |
- Proxies/load balancers. If your app runs behind one,
request.remote_addrwill be the proxy's IP, not the visitor's — every visitor would then wrongly show up with your load balancer's own reputation. The correct fix iswerkzeug.middleware.proxy_fix.ProxyFixconfigured for exactly as many trusted hops as you have, shown commented-out inexamples/example_app.py.IP2PROXY_TRUST_PROXY_HEADERtrusts whateverX-Forwarded-Forvalue shows up instead, which a visitor can forge if nothing upstream is sanitizing it — for a security-relevant signal like proxy detection, get this right rather than reaching for the header-trusting shortcut outside of local testing. - This is a signal, not a verdict.
is_proxy_flaggedreflects what a point-in-time IP-range database says about the address, not a live probe of the connection. Ranges get reassigned, home ISPs sometimes share ranges with hosting providers, and some legitimate users genuinely browse from VPNs. Treat it as one input alongside others (rate limiting, account history, etc.) rather than an automatic block/allow decision. - Session storage. As with Flask-IP2Location, Flask's default session
is a signed-but-not-encrypted cookie. Proxy-detection results are
usually fine to store there, but if you widen
IP2PROXY_FIELDSto includefraud_scoreor similar and have stricter requirements, consider a server-side session backend like Flask-Session. - LITE database fields. Free LITE editions only populate the columns their tier covers; anything else is detected automatically and dropped, as described above.
- IPv6. Whether IPv6 is resolved depends on which
.BINedition you download.
pip install -e ".[dev]"
pytestTests monkeypatch IP2Proxy.IP2Proxy with a fake in tests/conftest.py
whose get_all() return shape was verified against the real library's
source (not guessed), so no real .BIN database file is needed to run
the suite.
See the LICENSE file.