What is IP Geolocation?
IP geolocation is the mapping of an IP address to its real-world geographic location. By analyzing IP address ranges, routing data, and databases maintained by Regional Internet Registries (RIRs) and commercial services, you can determine where a device is physically located, what ISP it uses, and whether it’s a proxy, VPN, or datacenter IP.
How IP Geolocation Works
Lookup Process
interface IPGeolocationProcess {
step1: {
action: 'Capture IP address';
source: 'HTTP request, network connection';
example: '203.0.113.45';
};
step2: {
action: 'Query geolocation database';
providers: ['MaxMind GeoIP2', 'IP2Location', 'IPinfo', 'ipapi'];
data: 'IP ranges mapped to locations';
};
step3: {
action: 'Return location data';
fields: ['Country', 'Region', 'City', 'Latitude', 'Longitude', 'ISP', 'ASN'];
};
step4: {
action: 'Enrich with additional data';
optional: ['Timezone', 'Currency', 'Connection type', 'Proxy detection'];
};
}
Geolocation Data Structure
Typical Response Format
interface IPGeolocationData {
ip: string;
version: 'IPv4' | 'IPv6';
country: {
code: string; // ISO 3166-1 alpha-2
name: string;
flag: string; // Emoji flag
};
region: {
code: string; // State/province code
name: string;
};
city: string;
postal: string;
coordinates: {
latitude: number;
longitude: number;
accuracy: number; // Radius in km
};
timezone: {
name: string; // IANA timezone
offset: number; // UTC offset in seconds
current_time: string;
};
isp: {
name: string;
asn: number; // Autonomous System Number
organization: string;
};
connection: {
type: 'residential' | 'business' | 'cellular' | 'datacenter';
domain: string;
};
security: {
is_proxy: boolean;
is_vpn: boolean;
is_tor: boolean;
is_hosting: boolean;
threat_level: 'low' | 'medium' | 'high';
};
}
IP Geolocation APIs
Using Free APIs
class IPGeolocation {
async lookupIP(ip?: string): Promise<IPGeolocationData> {
// Using ipapi.co (free tier: 1000 requests/day)
const targetIP = ip || 'me'; // 'me' = your current IP
const response = await fetch(`https://ipapi.co/${targetIP}/json/`);
if (!response.ok) {
throw new Error(`Geolocation API error: ${response.status}`);
}
const data = await response.json();
return this.formatResponse(data);
}
async lookupIPWithProxy(ip: string, apiKey: string): Promise<IPGeolocationData> {
// Lookup IP through CORS proxy
const response = await fetch(
`https://corsproxy.io/?url=${encodeURIComponent(`https://ipapi.co/${ip}/json/`)}`,
{
headers: {
'x-cors-api-key': apiKey
}
}
);
const data = await response.json();
return this.formatResponse(data);
}
private formatResponse(data: any): IPGeolocationData {
return {
ip: data.ip,
version: data.version || 'IPv4',
country: {
code: data.country_code,
name: data.country_name,
flag: this.getCountryFlag(data.country_code)
},
region: {
code: data.region_code,
name: data.region
},
city: data.city,
postal: data.postal,
coordinates: {
latitude: data.latitude,
longitude: data.longitude,
accuracy: data.accuracy || 50
},
timezone: {
name: data.timezone,
offset: 0, // Not provided by ipapi.co
current_time: data.utc_offset
},
isp: {
name: data.org || 'Unknown',
asn: parseInt(data.asn?.replace('AS', '')) || 0,
organization: data.org || 'Unknown'
},
connection: {
type: this.detectConnectionType(data),
domain: ''
},
security: {
is_proxy: false,
is_vpn: false,
is_tor: false,
is_hosting: data.org?.toLowerCase().includes('hosting') || false,
threat_level: 'low'
}
};
}
private detectConnectionType(data: any): IPGeolocationData['connection']['type'] {
const org = data.org?.toLowerCase() || '';
if (org.includes('cellular') || org.includes('mobile')) {
return 'cellular';
} else if (org.includes('business') || org.includes('corporate')) {
return 'business';
} else if (org.includes('hosting') || org.includes('cloud')) {
return 'datacenter';
}
return 'residential';
}
private getCountryFlag(countryCode: string): string {
// Convert country code to flag emoji
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
async getCurrentLocation(): Promise<IPGeolocationData> {
return this.lookupIP();
}
async bulkLookup(ips: string[]): Promise<IPGeolocationData[]> {
const results: IPGeolocationData[] = [];
for (const ip of ips) {
const data = await this.lookupIP(ip);
results.push(data);
// Rate limiting: wait 1 second between requests
await new Promise(resolve => setTimeout(resolve, 1000));
}
return results;
}
}
// Usage
const geolocator = new IPGeolocation();
// Lookup specific IP
const location = await geolocator.lookupIP('8.8.8.8');
console.log(`Location: ${location.city}, ${location.country.name} ${location.country.flag}`);
console.log(`Coordinates: ${location.coordinates.latitude}, ${location.coordinates.longitude}`);
console.log(`ISP: ${location.isp.name}`);
// Lookup current IP
const myLocation = await geolocator.getCurrentLocation();
console.log('My location:', myLocation);
Advanced Geolocation with MaxMind
GeoIP2 Precision
interface MaxMindGeoIP2 {
service: 'MaxMind GeoIP2 Precision';
accuracy: 'City-level (99.8% country, 90% city)';
pricing: '$0.0003 - $0.001 per lookup';
features: ['Proxy detection', 'Connection type', 'Domain', 'Anonymous IP'];
}
class MaxMindGeolocation {
private accountId: string;
private licenseKey: string;
constructor(accountId: string, licenseKey: string) {
this.accountId = accountId;
this.licenseKey = licenseKey;
}
async lookupIP(ip: string): Promise<IPGeolocationData> {
const auth = Buffer.from(`${this.accountId}:${this.licenseKey}`).toString('base64');
const response = await fetch(
`https://geoip.maxmind.com/geoip/v2.1/city/${ip}`,
{
headers: {
'Authorization': `Basic ${auth}`
}
}
);
if (!response.ok) {
throw new Error(`MaxMind API error: ${response.status}`);
}
const data = await response.json();
return {
ip: data.traits.ip_address,
version: data.traits.ip_address.includes(':') ? 'IPv6' : 'IPv4',
country: {
code: data.country.iso_code,
name: data.country.names.en,
flag: this.getCountryFlag(data.country.iso_code)
},
region: {
code: data.subdivisions?.[0]?.iso_code || '',
name: data.subdivisions?.[0]?.names?.en || ''
},
city: data.city?.names?.en || '',
postal: data.postal?.code || '',
coordinates: {
latitude: data.location.latitude,
longitude: data.location.longitude,
accuracy: data.location.accuracy_radius
},
timezone: {
name: data.location.time_zone,
offset: 0,
current_time: ''
},
isp: {
name: data.traits.isp || 'Unknown',
asn: data.traits.autonomous_system_number || 0,
organization: data.traits.autonomous_system_organization || ''
},
connection: {
type: data.traits.connection_type || 'residential',
domain: data.traits.domain || ''
},
security: {
is_proxy: data.traits.is_anonymous_proxy || false,
is_vpn: data.traits.is_anonymous_vpn || false,
is_tor: data.traits.is_tor_exit_node || false,
is_hosting: data.traits.is_hosting_provider || false,
threat_level: this.calculateThreatLevel(data.traits)
}
};
}
private calculateThreatLevel(traits: any): 'low' | 'medium' | 'high' {
let score = 0;
if (traits.is_anonymous_proxy) score += 2;
if (traits.is_anonymous_vpn) score += 2;
if (traits.is_tor_exit_node) score += 3;
if (traits.is_hosting_provider) score += 1;
if (score >= 5) return 'high';
if (score >= 3) return 'medium';
return 'low';
}
private getCountryFlag(countryCode: string): string {
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
}
// Usage
const maxmind = new MaxMindGeolocation('account_id', 'license_key');
const location = await maxmind.lookupIP('8.8.8.8');
console.log('MaxMind location:', location);
Geolocation-Based Access Control
Geo-Blocking Implementation
class GeoAccessControl {
private allowedCountries: Set<string>;
private blockedCountries: Set<string>;
private geolocator: IPGeolocation;
constructor() {
this.allowedCountries = new Set();
this.blockedCountries = new Set();
this.geolocator = new IPGeolocation();
}
allowCountries(countries: string[]): void {
countries.forEach(country => this.allowedCountries.add(country.toUpperCase()));
}
blockCountries(countries: string[]): void {
countries.forEach(country => this.blockedCountries.add(country.toUpperCase()));
}
async isAllowed(ip: string): Promise<{
allowed: boolean;
reason: string;
location: IPGeolocationData;
}> {
const location = await this.geolocator.lookupIP(ip);
const countryCode = location.country.code.toUpperCase();
// Check blocked list first
if (this.blockedCountries.has(countryCode)) {
return {
allowed: false,
reason: `Country ${location.country.name} is blocked`,
location
};
}
// Check allowed list (if specified)
if (this.allowedCountries.size > 0 && !this.allowedCountries.has(countryCode)) {
return {
allowed: false,
reason: `Country ${location.country.name} is not in allowed list`,
location
};
}
// Check for proxy/VPN
if (location.security.is_proxy || location.security.is_vpn) {
return {
allowed: false,
reason: 'Proxy or VPN detected',
location
};
}
return {
allowed: true,
reason: 'Access granted',
location
};
}
async middleware(req: any, res: any, next: any): Promise<void> {
const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.socket.remoteAddress;
const result = await this.isAllowed(ip);
if (!result.allowed) {
res.status(403).json({
error: 'Access Denied',
reason: result.reason,
location: {
country: result.location.country.name,
city: result.location.city
}
});
return;
}
// Attach location to request
(req as any).geolocation = result.location;
next();
}
}
// Usage - Express middleware
import express from 'express';
const app = express();
const geoControl = new GeoAccessControl();
// Allow only US, UK, CA
geoControl.allowCountries(['US', 'UK', 'CA']);
// Block specific countries
geoControl.blockCountries(['CN', 'RU']);
// Apply geo-blocking middleware
app.use(async (req, res, next) => {
await geoControl.middleware(req, res, next);
});
app.get('/api/data', (req, res) => {
const location = (req as any).geolocation;
res.json({
message: 'Access granted',
your_location: `${location.city}, ${location.country.name}`
});
});
Proxy Detection
Identifying Proxy/VPN IPs
interface ProxyDetection {
datacenter: {
method: 'Check ASN against known hosting providers';
indicators: ['AWS', 'DigitalOcean', 'OVH', 'Hetzner'];
accuracy: '99%';
};
residential: {
method: 'Behavioral analysis and database lookup';
indicators: ['Suspicious patterns', 'Known proxy IPs'];
accuracy: '85%';
};
vpn: {
method: 'Known VPN provider IP ranges';
indicators: ['NordVPN', 'ExpressVPN', 'ProtonVPN'];
accuracy: '95%';
};
tor: {
method: 'Tor exit node list';
source: 'https://check.torproject.org/exit-addresses';
accuracy: '100%';
};
}
class ProxyDetector {
private geolocator: IPGeolocation;
constructor() {
this.geolocator = new IPGeolocation();
}
async detectProxy(ip: string): Promise<{
is_proxy: boolean;
proxy_type: string | null;
confidence: number;
details: any;
}> {
const location = await this.geolocator.lookupIP(ip);
let isProxy = false;
let proxyType: string | null = null;
let confidence = 0;
// Check if ISP is known hosting provider
const hostingKeywords = ['hosting', 'cloud', 'server', 'datacenter', 'vps'];
const isHosting = hostingKeywords.some(keyword =>
location.isp.name.toLowerCase().includes(keyword)
);
if (isHosting) {
isProxy = true;
proxyType = 'datacenter';
confidence = 0.99;
}
// Check known VPN providers
const vpnKeywords = ['vpn', 'proxy', 'private', 'secure'];
const isVPN = vpnKeywords.some(keyword =>
location.isp.name.toLowerCase().includes(keyword)
);
if (isVPN) {
isProxy = true;
proxyType = 'vpn';
confidence = 0.95;
}
// Check security flags
if (location.security.is_proxy || location.security.is_vpn) {
isProxy = true;
proxyType = location.security.is_vpn ? 'vpn' : 'proxy';
confidence = 0.90;
}
if (location.security.is_tor) {
isProxy = true;
proxyType = 'tor';
confidence = 1.0;
}
return {
is_proxy: isProxy,
proxy_type: proxyType,
confidence,
details: location
};
}
async checkIPReputation(ip: string): Promise<{
safe: boolean;
threat_score: number;
reasons: string[];
}> {
const detection = await this.detectProxy(ip);
const reasons: string[] = [];
let threatScore = 0;
if (detection.is_proxy) {
reasons.push(`Detected as ${detection.proxy_type}`);
threatScore += 50;
}
if (detection.details.security.is_hosting) {
reasons.push('Datacenter IP');
threatScore += 30;
}
if (detection.details.security.is_tor) {
reasons.push('Tor exit node');
threatScore += 70;
}
return {
safe: threatScore < 50,
threat_score: threatScore,
reasons
};
}
}
// Usage
const detector = new ProxyDetector();
const proxyCheck = await detector.detectProxy('8.8.8.8');
console.log('Proxy detection:', proxyCheck);
const reputation = await detector.checkIPReputation('8.8.8.8');
console.log('IP reputation:', reputation);
Distance Calculation
Geographic Distance Between IPs
class GeoDistance {
private geolocator: IPGeolocation;
constructor() {
this.geolocator = new IPGeolocation();
}
haversineDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
): number {
const R = 6371; // Earth radius in km
const dLat = this.toRadians(lat2 - lat1);
const dLon = this.toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.toRadians(lat1)) *
Math.cos(this.toRadians(lat2)) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
private toRadians(degrees: number): number {
return degrees * (Math.PI / 180);
}
async distanceBetweenIPs(ip1: string, ip2: string): Promise<{
distance_km: number;
distance_mi: number;
location1: string;
location2: string;
}> {
const loc1 = await this.geolocator.lookupIP(ip1);
const loc2 = await this.geolocator.lookupIP(ip2);
const distanceKm = this.haversineDistance(
loc1.coordinates.latitude,
loc1.coordinates.longitude,
loc2.coordinates.latitude,
loc2.coordinates.longitude
);
return {
distance_km: Math.round(distanceKm),
distance_mi: Math.round(distanceKm * 0.621371),
location1: `${loc1.city}, ${loc1.country.name}`,
location2: `${loc2.city}, ${loc2.country.name}`
};
}
}
// Usage
const geoDistance = new GeoDistance();
const distance = await geoDistance.distanceBetweenIPs('8.8.8.8', '1.1.1.1');
console.log(`Distance: ${distance.distance_km} km (${distance.distance_mi} miles)`);
console.log(`From ${distance.location1} to ${distance.location2}`);
Best Practices
Geolocation Guidelines
interface GeolocationBestPractices {
accuracy: {
rule: 'Never rely on city-level accuracy for critical decisions';
reason: 'Geolocation has ~50km accuracy radius';
alternative: 'Use country-level for most applications';
};
privacy: {
rule: 'Store geolocation data securely and compliantly';
reason: 'Geolocation is personal data under GDPR';
requirements: ['User consent', 'Data retention limits', 'Secure storage'];
};
caching: {
rule: 'Cache geolocation results to reduce API costs';
duration: '24 hours for most IPs';
exception: 'Mobile IPs change frequently';
};
fallback: {
rule: 'Always have fallback for geolocation failures';
reason: 'API rate limits, network errors';
fallback: 'Default to country-level or no restriction';
};
}