So I Finally Sat Down And Built That Login Thing I Kept Complaining About

So I finally sat down and built the thing I've been complaining about for, honestly, months now. Small thing. Not a big deal. But it's been bugging me enough that I want to actually write about it instead of just mentioning it to whoever's unlucky enough to be sitting near me.
Okay backing up. Here's the actual problem, because I realize I'm three sentences in and haven't explained anything yet.
Say someone logs into your account from New York. Cool, normal, whatever. Then forty minutes later, same account, logs in again, except now it's from Lagos. That's about 8,400 km away, give or take. And here's the thing, nobody does that trip in forty minutes. Doesn't matter how you slice it, there's no flight, no anything, that gets a person from one to the other that fast. So either your account holder discovered teleportation, or somebody else has their password.
That idea isn't mine, to be clear. I've read some version of it a dozen times in security blogs, in vendor docs, in random Medium posts. Store the last login, compare the distance and time to the new one, flag it if the math doesn't work out. It's a genuinely good idea and it keeps getting explained the exact same way, over and over. What almost nobody does is actually hand you the code for it. Just the concept. Again.
Which, fine, I get it, explaining an idea is easier than building it properly. I know because I tried to build it properly and the "properly" part is where it got annoying.
Like, what happens on someone's very first login? There's nothing to compare it against yet, so does it just... flag everything? (No, obviously not, but you have to actually handle that case, it doesn't handle itself.) What about two logins from the same city, five seconds apart, because your ISP handed you a slightly different IP for no reason? That'll false-positive constantly if you don't put a floor on the distance. And I actually broke this once early on with a timing bug, got a negative number out of a division, don't ask, it was dumb, but that's exactly the kind of thing that never shows up when someone's just explaining the concept in a blog post.
Anyway. I built it. It's called impossible-travel-guard, it's free, it's open source, MIT license, no dependencies. Node and TypeScript. You install it, call one function right after your login check passes, and it tells you whether the trip implied by the last two logins was even physically possible.
import { TravelGuard } from "impossible-travel-guard";
const guard = new TravelGuard();
const result = await guard.check({
userId: "user_123",
latitude: 6.5244,
longitude: 3.3792,
timestamp: Date.now(),
});
if (result.flagged) {
// ask for MFA, send an alert, whatever fits your app
}
That's really it. First login for anyone is never flagged since there's nothing to compare yet, it just becomes the starting point.
Oh, I should mention, there's a stat that actually got me to take this more seriously than "huh, neat idea." The FBI's Internet Crime Complaint Center had something like 4,700 account takeover complaints in 2025, with losses around $359.7 million. And the part that stuck with me was they mentioned that once someone's actually inside an account, it's common for a bunch of transactions to hit multiple banks within minutes. Not a person doing that by hand, obviously. That's automation moving the second it's past your login screen. Kind of puts "eh, this is a nice-to-have" into perspective.
I'll be honest about where this thing falls apart too, because I don't think it's fair to write about a tool without saying where it's weak. It has no opinion on VPNs or device fingerprints or any of that, it was never trying to replace those checks, just cover something they miss. And it genuinely cannot tell you why a trip looks impossible, only that it does. A company VPN bouncing between two data centers looks exactly like a real account takeover to this thing. So does someone behind Apple's Private Relay, or switching from their phone's carrier to home wifi mid-session. Every version of this idea anyone builds runs into the same noise, mine's not special in that regard. So don't hard block on a flag. Ask for more verification instead, log enough detail that you can actually go back and look at a handful by hand until you trust your threshold.
For actually getting the location data, by the way, the library doesn't call any geolocation API on its own, that was on purpose so it works with whatever you're already using. If you don't have something already, IPGeolocation.io's free tier covers what this needs, and so does ipapi, or MaxMind's GeoLite2 if you'd rather run something locally. Any of them work fine here, this doesn't need anything fancy.
There's a live demo too, you can just try it in a browser, pick two cities and a time gap and watch it flag or not. I built that mostly because I realized halfway through that a test suite passing on my own machine wasn't proof of anything to anyone who wasn't me. Which, yeah, kind of an obvious realization in hindsight, but it took me longer to get there than I'd like to admit.
Demo's here: https://furqan-ashraf.github.io/impossible-travel-guard/
Code's here: https://github.com/Furqan-Ashraf/impossible-travel-guard
And it's on npm if you just want to install it directly: impossible-travel-guard
Anyway. That's the thing I built. Let me know if you find a case that breaks it, I'd actually rather know.