How to Detect Suspicious Logins Using IP Geolocation?
In an era where credential stuffing and account takeover (ATO) attacks are automated at scale, relying solely on a username and password is a significant security risk. For backend engineers, adding a layer of IP intelligence is one of the most effective ways to identify unauthorized access without introducing excessive friction for legitimate users.
This post explores the technical implementation of IP-based login monitoring, focusing on how to use geolocation and network metadata to flag suspicious behavior.

What Is a Suspicious Login?
A login is considered suspicious when it deviates from a user’s established behavior patterns. Common examples include:
- Login from a new country or region
- Sudden country change within a short time window
- Login from known proxy, VPN, or hosting infrastructure
- Impossible travel scenarios (e.g., India → US in 10 minutes)
- Repeated failed logins from different IPs
IP geolocation does not replace authentication or MFA, but it acts as an early warning signal that helps trigger additional verification or security actions.
How IP Geolocation Helps Detect Suspicious Logins
IP geolocation maps an IP address to geographic and network metadata, such as:
- Country, region, and city
- Timezone
- ISP or organization
- ASN (Autonomous System Number)
- Network type (residential, mobile, hosting)
By comparing the login IP’s metadata with historical login data for the same user, backend systems can detect anomalies in near real time.
The Role of IP Intelligence in Security
IP geolocation is more than just mapping an IP address to a city. In a security context, it provides contextual metadata about the connection. When a user attempts to log in, the IP address carries signals that can be cross-referenced with historical data or security policies.
By analyzing the source IP, developers can detect:
- Impossible Travel: A user logs in from New York and then from London 30 minutes later.
- Geographic Anomalies: Access from a country where the user has never been or where the business does not operate.
- Masking Infrastructure: Traffic originating from Tor exit nodes, public proxies, or data center IP ranges (often used by bots).
- Network Inconsistency: A sudden switch from a residential ISP to a known hosting provider.
Technical Implementation: The Logic Flow
To implement this, your authentication service should capture the client's IP address and query an IP intelligence API before or immediately after processing the credentials.
1. Capturing the IP
In a typical Node.js or Python backend, you extract the IP from the request headers. If you are behind a load balancer (like Nginx or AWS ALB), look for the X-Forwarded-For header.
2. Querying the IP2GeoAPI
A standard GET request to a geolocation service returns a JSON object containing the location and network data. Example Request (cURL):
curl -X GET "https://api.ip2geoapi.com/ip/8.8.8.8?key=YOUR_API_KEY"
You can get a free API key from us, which includes 100,000 free requests per month. Example JSON Response:
{
"success": true,
"ip": "8.8.8.8",
"version": "ipv4",
"geo": {
"city": "Chicago",
"country": "United States",
"countryCode": "US",
"region": null,
"regionCode": null,
"latitude": 37.751,
"longitude": -97.822,
"postalCode": null,
"geonameId": 6252001,
"accuracyRadius": 1000,
"metroCode": null,
"continentName": "North America",
"continentCode": "NA",
"isEuMember": false
},
"countryInfo": {
"name": "United States of America",
"alpha2Code": "US",
"alpha3Code": "USA",
"flag": "https://api.ip2geoapi.com/assets/flags/us.svg",
"callingCodes": [
"1"
],
"currencies": [
{
"code": "USD",
"name": "United States dollar",
"symbol": "$"
}
],
"languages": [
{
"iso639_1": "en",
"iso639_2": "eng",
"name": "English",
"nativeName": "English"
}
]
},
"timezoneInfo": {
"timezone": "America/Chicago",
"utcOffsetSeconds": -21600,
"utcOffsetText": "-06:00",
"utcOffsetHours": -6,
"isDst": false,
"abbreviation": "CST",
"localTime": "2026-01-14T21:48:33-06:00"
},
"network": {
"cidr": "8.8.8.8/32",
"prefixLen": 32,
"asn": 15169,
"asFormatted": "AS15169",
"asName": "GOOGLE",
"isp": "Google",
"organization": "Google",
"connectionType": "Corporate",
"mobile": {
"mcc": null,
"mnc": null
}
},
"asDetails": {
"asn": 15169,
"abuser_score": "0.001 (Low)",
"descr": "GOOGLE, US",
"country": "us",
"active": true,
"org": "Google LLC",
"domain": "google.com",
"abuse": "[email protected]",
"type": "hosting",
"created": "2000-03-30",
"updated": "2012-02-24",
"rir": "ARIN"
},
"security": {
"isHosting": true,
"isProxy": false,
"proxyType": null,
"isVpn": false,
"vpnProvider": null,
"vpnProviderUrl": null,
"isTor": false,
"isAnonymous": true,
"trustScore": 65,
"riskLevel": "medium"
}
}
3. Verification Logic
Once the data is retrieved, the backend evaluates it against several rules:
-
Location Delta: Compare the
countryCodeorcitywith the last 5 successful logins stored in your database. -
Proxy/VPN/Tor Detection: If
isProxy,isVpnorisToris true, the user is likely masking their identity. This should trigger a Mandatory Multi-Factor Authentication (MFA) challenge. -
Hosting IP: If the
isHostingfield turns true, it indicates a cloud provider (e.g., "DigitalOcean", "AWS") rather than a consumer ISP (e.g., "Comcast", "Verizon"), it is often a sign of scripted/bot traffic. -
The final decision should be based on combined signals, not a single field.
Blocking Suspicious Logins: Python Implementation
Below is a clean, production-style Python example showing how to implement suspicious login detection using IP geolocation, based exactly on your real JSON structure from ip2geoapi. High-Level Logic
We will:
-
Fetch IP intelligence from ip2geoapi
-
Compare it with the user’s previous login metadata
-
Assign a risk decision: allow, challenge, or block
-
Return a structured decision object Example Assumptions:
-
You store last successful login per user
-
You already validated username/password
-
This code runs after authentication, before session creation
Example Stored Login Metadata (DB / Cache):
LAST_LOGIN = {
"country_code": "IN",
"asn": 55836,
"timezone": "Asia/Kolkata",
"timestamp": 1736856000 # unix epoch
}
Core Implementation
import requests
import time
from math import radians, cos, sin, asin, sqrt
API_KEY = "YOUR_API_KEY"
API_URL = "https://api.ip2geoapi.com/ip/{}?key={}"
TIME_TRAVEL_LIMIT_KM = 5000
TIME_WINDOW_SECONDS = 3600 # 1 hour
def fetch_ip_intel(ip: str) -> dict:
resp = requests.get(API_URL.format(ip, API_KEY), timeout=20)
resp.raise_for_status()
data = resp.json()
if not data.get("success"):
raise ValueError("IP intelligence lookup failed")
return data
def haversine(lat1, lon1, lat2, lon2):
r = 6371 # Earth radius in km
dlat = radians(lat2 - lat1)
dlon = radians(lon2 - lon1)
a = sin(dlat/2)**2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon/2)**2
c = 2 * asin(sqrt(a))
return r * c
def evaluate_login_risk(ip_data: dict, last_login: dict | None):
risk_score = 0
reasons = []
geo = ip_data["geo"]
network = ip_data["network"]
security = ip_data["security"]
tz = ip_data["timezoneInfo"]
# Hosting / VPN / Proxy signals
if security["isHosting"]:
risk_score += 40
reasons.append("hosting_ip")
if security["isVpn"] or security["isProxy"] or security["isTor"]:
risk_score += 50
reasons.append("anonymized_network")
# Anonymous flag
if security["isAnonymous"]:
risk_score += 20
reasons.append("anonymous_network")
# Country change
if last_login:
if geo["countryCode"] != last_login["country_code"]:
risk_score += 25
reasons.append("country_change")
# ASN change
if network["asn"] != last_login["asn"]:
risk_score += 15
reasons.append("asn_change")
# Impossible travel
if geo["latitude"] and geo["longitude"]:
distance = haversine(
geo["latitude"],
geo["longitude"],
last_login.get("latitude", geo["latitude"]),
last_login.get("longitude", geo["longitude"]),
)
delta = time.time() - last_login["timestamp"]
if delta < TIME_WINDOW_SECONDS and distance > TIME_TRAVEL_LIMIT_KM:
risk_score += 40
reasons.append("impossible_travel")
# Trust score from API
if security["trustScore"] < 50:
risk_score += 20
reasons.append("low_trust_score")
return risk_score, reasons
def decision_from_score(score: int):
if score >= 70:
return "block"
elif score >= 40:
return "challenge" # MFA / OTP / Email verification
return "allow"
def process_login(ip: str, last_login: dict | None):
ip_data = fetch_ip_intel(ip)
score, reasons = evaluate_login_risk(ip_data, last_login)
decision = decision_from_score(score)
return {
"decision": decision,
"risk_score": score,
"reasons": reasons,
"country": ip_data["geo"]["countryCode"],
"asn": ip_data["network"]["asn"],
"risk_level": ip_data["security"]["riskLevel"]
}
Example Output:
{
"decision": "challenge",
"risk_score": 65,
"reasons": [
"hosting_ip",
"country_change",
"anonymous_network"
],
"country": "US",
"asn": 15169,
"risk_level": "medium"
}
How to Use This in Production?
- allow → create session normally
- challenge → trigger MFA / email verification
- block → deny login + alert security
Real-World Use Cases
Account Takeover Prevention
If a user typically logs in from India and suddenly attempts to log in from Eastern Europe, the system can flag the attempt as high risk and require MFA.
SaaS Admin Account Protection
Admin or owner accounts can be restricted to specific countries or ASNs. Any login outside those parameters can be blocked outright.
Consumer Apps and E-commerce
For consumer applications, suspicious logins may not be blocked but can trigger alerts or email notifications.
Compliance and Audit Logging
Geolocation data is often stored alongside login logs to support forensic analysis and compliance requirements.
Accuracy Limitations and Edge Cases
IP geolocation is probabilistic, not exact. Developers should account for the following limitations:
- Mobile networks may route traffic through centralized gateways
- Corporate VPNs can appear as legitimate but distant locations
- ISPs reassign IPs, causing occasional location shifts
- City-level accuracy is less reliable than country-level data
Because of these factors, geolocation should never be the sole factor for blocking access.
Common Mistakes to Avoid
- Blocking users solely based on country change. Instead, you can consider blocking traffic from risky countries.
- Ignoring IPv6 addresses
- Trusting unvalidated X-Forwarded-For headers
- Treating VPN usage as always malicious
- Storing raw IPs without privacy consideration
A balanced approach reduces false positives while still improving security.
Security Considerations
- Fail-Open vs. Fail-Closed: If the geolocation API is unreachable, should you block the login? For most SaaS apps, "Fail-Open" (allow the login but log the error) is the best balance of UX and security.
- Caching: You can cache the geolocation of an IP for 24-48 hours to save on API credits and reduce latency, as IP assignments for residential users do not change that frequently.
- Data Privacy: Ensure that your storage of IP and location data complies with GDPR/CCPA. Generally, storing city-level data is safer than storing exact coordinates.
- Cache geolocation results for short durations (e.g., 5–15 minutes)
- Apply IP lookups after password validation, not before
- Log decisions and signals for audit and tuning
- Combine IP geolocation with device fingerprinting and MFA
- Avoid synchronous API calls if your login flow is extremely latency-sensitive (use async or parallel execution)
Conclusion
IP geolocation is a practical, low-friction way to detect suspicious login behavior when applied correctly. It provides geographic and network context that helps backend systems identify anomalies without disrupting legitimate users.
When combined with historical login data and sensible risk rules, IP geolocation significantly strengthens account security. APIs like ip2geoapi.com make this easy to implement at scale, with accurate data, IPv4/IPv6 support, and a generous free tier suitable for production testing. Used thoughtfully, IP geolocation becomes a reliable signal—not a blunt instrument—in modern authentication systems.
Ready to secure your app? Sign up for IP2GeoAPI and get your first 100K requests free.