DEV Community

miccho27
miccho27

Posted on

IP Geolocation for Web Apps: Compliance, Localization, and Fraud Detection

Know Where Your Users Are: IP Geolocation Without the Complexity

Web applications need to know where users are for three reasons:

  1. Compliance: GDPR requires knowing if users are in the EU
  2. Localization: Serve content in the right currency, language, timezone
  3. Security: Block access from suspicious locations, detect account takeovers

Building geolocation infrastructure is expensive. Buying MaxMind licenses is expensive. There's a better way.

Real-World Use Case: Compliance + Localization

You're launching a SaaS product globally. Your first request gets a user's IP. Now you need to:

  1. Determine if they're in the EU (GDPR applies)
  2. Show pricing in their local currency
  3. Display the app in their language
  4. Flag if they're accessing from an unusual location (security)

All from a single IP address.

Python Implementation: Localize on Signup

import requests
from decimal import Decimal
from enum import Enum

RAPIDAPI_KEY = "YOUR_RAPIDAPI_KEY"
RAPIDAPI_HOST = "ip-geolocation-api.p.rapidapi.com"

class CurrencyCode(Enum):
    USD = "USD"
    EUR = "EUR"
    GBP = "GBP"
    JPY = "JPY"

CURRENCY_BY_COUNTRY = {
    "US": "USD",
    "CA": "CAD",
    "GB": "GBP",
    "DE": "EUR",
    "FR": "EUR",
    "JP": "JPY",
    "AU": "AUD",
    "IN": "INR",
}

def geolocate_ip(ip_address: str) -> dict:
    """
    Get geolocation data for an IP address.

    Returns:
        {
            "ip": "203.0.113.45",
            "country": "US",
            "country_name": "United States",
            "region": "California",
            "city": "San Francisco",
            "latitude": 37.7749,
            "longitude": -122.4194,
            "timezone": "America/Los_Angeles",
            "isp": "Comcast Cable"
        }
    """
    url = "https://ip-geolocation-api.p.rapidapi.com/ipgeo"

    params = {"ip": ip_address}

    headers = {
        "X-RapidAPI-Key": RAPIDAPI_KEY,
        "X-RapidAPI-Host": RAPIDAPI_HOST
    }

    response = requests.get(url, headers=headers, params=params)
    response.raise_for_status()

    return response.json()

def get_user_locale(ip_address: str) -> dict:
    """
    Determine user locale settings based on IP geolocation.
    Used to customize signup flow.
    """
    geo = geolocate_ip(ip_address)

    country_code = geo['country']
    currency = CURRENCY_BY_COUNTRY.get(country_code, "USD")

    # GDPR applies to EU/EEA residents
    eu_countries = {
        "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR",
        "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL",
        "PL", "PT", "RO", "SK", "SI", "ES", "SE"
    }
    gdpr_applies = country_code in eu_countries

    return {
        "country_code": country_code,
        "country": geo['country_name'],
        "timezone": geo['timezone'],
        "currency": currency,
        "language": get_language_for_country(country_code),
        "gdpr_applies": gdpr_applies,
        "region": geo.get('region'),
        "city": geo.get('city')
    }

def get_language_for_country(country_code: str) -> str:
    """Map country code to language code."""
    lang_map = {
        "US": "en", "GB": "en", "CA": "en",
        "DE": "de", "AT": "de", "CH": "de",
        "FR": "fr", "BE": "fr",
        "ES": "es", "MX": "es",
        "IT": "it",
        "JP": "ja",
        "CN": "zh",
        "BR": "pt",
    }
    return lang_map.get(country_code, "en")

# Example: User signup
user_ip = "203.0.113.45"
locale = get_user_locale(user_ip)

# Customize signup experience
print(f"Welcome! We detect you're in {locale['country']}")
print(f"Showing prices in {locale['currency']}")
print(f"Setting timezone to {locale['timezone']}")

if locale['gdpr_applies']:
    print("You're in the EU. Showing GDPR consent dialog...")
Enter fullscreen mode Exit fullscreen mode

Express.js: Localized Pricing

const axios = require("axios");
const express = require("express");
const app = express();

const geolocateIP = async (ipAddress) => {
  const response = await axios.get(
    "https://ip-geolocation-api.p.rapidapi.com/ipgeo",
    {
      params: { ip: ipAddress },
      headers: {
        "X-RapidAPI-Key": process.env.RAPIDAPI_KEY,
        "X-RapidAPI-Host": "ip-geolocation-api.p.rapidapi.com"
      }
    }
  );

  return response.data;
};

// Pricing in different currencies
const PRICING = {
  USD: { starter: 29, pro: 99, enterprise: 499 },
  EUR: { starter: 25, pro: 85, enterprise: 425 },
  GBP: { starter: 22, pro: 75, enterprise: 380 },
  JPY: { starter: 3200, pro: 10900, enterprise: 54900 },
};

const CURRENCY_BY_COUNTRY = {
  US: "USD", CA: "USD", GB: "GBP", DE: "EUR", FR: "EUR",
  JP: "JPY", AU: "AUD", IN: "INR", BR: "BRL", MX: "MXN",
};

app.get("/api/pricing", async (req, res) => {
  try {
    // Get user's real IP (handle proxies)
    const userIP = 
      req.headers["cf-connecting-ip"] ||
      req.headers["x-forwarded-for"]?.split(",")[0].trim() ||
      req.socket.remoteAddress;

    // Geolocate
    const geo = await geolocateIP(userIP);
    const currency = CURRENCY_BY_COUNTRY[geo.country] || "USD";

    // Return localized pricing
    const pricing = PRICING[currency] || PRICING.USD;

    res.json({
      currency,
      country: geo.country_name,
      region: geo.region,
      pricing,
      gdpr_applies: ["AT", "DE", "FR"].includes(geo.country)
    });

  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Endpoint: Get user's timezone
app.get("/api/user-timezone", async (req, res) => {
  try {
    const userIP = 
      req.headers["cf-connecting-ip"] ||
      req.headers["x-forwarded-for"]?.split(",")[0].trim() ||
      req.socket.remoteAddress;

    const geo = await geolocateIP(userIP);

    res.json({
      timezone: geo.timezone,
      latitude: geo.latitude,
      longitude: geo.longitude
    });

  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Security: Fraud Detection by Unusual Location

Detect account takeovers by comparing login location to previous logins:

from geopy.distance import geodesic

def detect_suspicious_login(user_id: str, login_ip: str, previous_logins: list) -> dict:
    """
    Detect if a login is from an unusual location.
    Flags if:
    - User logged in from different country
    - Impossible travel (too far too fast)
    """

    geo = geolocate_ip(login_ip)

    if not previous_logins:
        return {
            "suspicious": False,
            "reason": "first_login",
            "country": geo['country']
        }

    last_login = previous_logins[-1]

    # Check country change
    if geo['country'] != last_login['country']:
        return {
            "suspicious": True,
            "reason": "country_change",
            "from": last_login['country'],
            "to": geo['country']
        }

    # Check impossible travel
    current_coords = (geo['latitude'], geo['longitude'])
    previous_coords = (
        last_login['latitude'],
        last_login['longitude']
    )

    # Distance in kilometers
    distance_km = geodesic(current_coords, previous_coords).km

    # Time since last login in hours
    time_hours = (
        datetime.now() - datetime.fromisoformat(last_login['timestamp'])
    ).total_seconds() / 3600

    # Required speed (assuming ~900 km/h for fastest commercial flights)
    required_speed_kmh = distance_km / time_hours

    if required_speed_kmh > 900:
        return {
            "suspicious": True,
            "reason": "impossible_travel",
            "distance_km": distance_km,
            "time_hours": time_hours,
            "required_speed_kmh": round(required_speed_kmh, 2)
        }

    return {
        "suspicious": False,
        "reason": "normal",
        "country": geo['country']
    }

# Example: Detect account takeover
user_previous_logins = [
    {
        "country": "US",
        "latitude": 37.7749,
        "longitude": -122.4194,
        "timestamp": "2024-04-12T10:30:00Z"
    }
]

suspicious = detect_suspicious_login(
    user_id="user123",
    login_ip="203.0.113.200",  # Different country
    previous_logins=user_previous_logins
)

if suspicious['suspicious']:
    print(f"Alert! {suspicious['reason']}")
    # Trigger 2FA, email user, etc.
Enter fullscreen mode Exit fullscreen mode

API Parameters

Parameter Type Notes
ip string IP address to geolocate

Response Format

{
  "ip": "203.0.113.45",
  "country": "US",
  "country_name": "United States",
  "region": "California",
  "city": "San Francisco",
  "latitude": 37.7749,
  "longitude": -122.4194,
  "timezone": "America/Los_Angeles",
  "isp": "Comcast Cable",
  "org": "Comcast"
}
Enter fullscreen mode Exit fullscreen mode

GDPR Compliance Notes

If you geolocate users, you're processing personal data. Keep in mind:

  • Lawful basis: Geolocation for compliance/security is typically legitimate interest
  • Data retention: Don't store geolocation longer than needed
  • User rights: Users can request geolocation data deletion
  • Privacy policy: Disclose that you geolocate by IP
# Example: GDPR-compliant geolocation logging
def log_geolocation_for_compliance(user_id: str, ip: str, purpose: str):
    """
    Log geolocation with GDPR justification.
    purposes: "compliance", "fraud_detection", "localization"
    """
    geo = geolocate_ip(ip)

    # Store minimally: only country code + timestamp + purpose
    store_in_db({
        "user_id": user_id,
        "country": geo['country'],
        "purpose": purpose,
        "timestamp": datetime.now().isoformat(),
        "retention_until": (datetime.now() + timedelta(days=90)).isoformat()
    })
Enter fullscreen mode Exit fullscreen mode

Use Cases

Perfect for:

  • Localized pricing and currencies
  • GDPR compliance detection
  • Content localization
  • Fraud detection by geolocation
  • Timezone detection
  • Regional service availability

Not ideal for:

  • High-precision location (use GPS for that)
  • Streaming IP lookups (batch with local cache)
  • Emergency services (unreliable accuracy)

Pricing

Plan Cost Lookups/mo Rate Limit
Free $0 1,000 1 req/sec
Pro $9.99 100,000 10 req/sec
Ultra $49.99 1,000,000 50 req/sec

Final Thoughts

IP geolocation is essential for modern web apps—compliance, localization, and security all depend on knowing where users are. APIs make it trivial to add geolocation without building infrastructure.

Get started free at RapidAPI Marketplace.


What geolocation features would you add to your app? Share your ideas in the comments!

Top comments (0)