Olympus Docs
CookbookDefensive security

Detect impossible travel

Flag logins from physically impossible locations

"Impossible travel" = a user logs in from London, then 10 minutes later from Tokyo. Almost certainly: stolen credentials being used from a botnet, or VPN-hopping. Flag it.

Approach

Olympus's SDK records every session's geo-location in the locations table. Compare each new login to the most recent one:

  • If physical distance > N km
  • And time delta < M minutes
  • Then flag (and/or block, alert, require step-up).

Implementation

Add a check on login completion

In the SDK or your custom Kratos hook:

async function checkImpossibleTravel(identityId: string, newIp: string) {
  const latest = await db`
    SELECT ip, country, city, created_at
    FROM locations
    WHERE identity_id = ${identityId}
    ORDER BY created_at DESC
    LIMIT 1
  `.first();

  if (!latest) return; // First login, no comparison.

  const newGeo = await geoLookup(newIp); // MaxMind, ipdata.co, etc.
  const oldGeo = await geoLookup(latest.ip);

  const distanceKm = haversine(
    newGeo.latitude, newGeo.longitude,
    oldGeo.latitude, oldGeo.longitude
  );
  const minutesSince = (Date.now() - new Date(latest.created_at).getTime()) / 60_000;

  // Threshold: 600 km in 30 minutes
  const maxKmPerMinute = 600 / 30; // ~20 km/min (faster than commercial flight)
  if (distanceKm > maxKmPerMinute * minutesSince) {
    return { suspicious: true, oldGeo, newGeo, distanceKm, minutesSince };
  }

  return { suspicious: false };
}

Take action

const check = await checkImpossibleTravel(identity.id, sourceIp);
if (check?.suspicious) {
  // Option 1: Force step-up MFA
  return { redirect: "/self-service/login/browser?aal=aal2&refresh=true&reason=impossible_travel" };

  // Option 2: Email the user
  await sendSecurityAlert(identity.email, {
    type: "impossible_travel",
    oldLocation: `${check.oldGeo.city}, ${check.oldGeo.country}`,
    newLocation: `${check.newGeo.city}, ${check.newGeo.country}`,
  });

  // Option 3: Block outright (aggressive)
  return { error: "Login from unusual location blocked" };
}

Edge cases

  • VPN users: legitimately hop locations. Don't be too aggressive.
  • Mobile users on roaming: similar, IP geo flips frequently.
  • Power users with global presence: traveling executives, devops, etc.

Tune the thresholds (km, minutes) based on your user base. Start with high thresholds (1000km in 15min) and tighten as you collect data.

Combine with other signals

Impossible travel alone is a weak signal. Combine with:

  • Device fingerprint: new browser fingerprint + new location is more suspicious than known browser + new location.
  • User agent change: different OS in the same session window.
  • Failed-login rate: recent failed logins on the account add suspicion.

Risk-based step-up is the right pattern: any combination of signals can trigger AAL2 without fully blocking.

Privacy

Geo-IP lookups happen server-side. Don't expose user IPs to your frontend. The location stored in the locations table is PII, handle per your DPA.

On this page