All guides
Security — GuideSecurity

Why Client-Side Fake GPS Checks Aren't Enough

Mock-location flags can be hidden on a compromised device. Here is how to validate location on the server with speed, time and integrity signals.

Syed Wajeeh Haider
Syed Wajeeh Haider
Full Stack Software Engineer
Sep 28, 20265 min read
#Location#Security#Backend#Anti-Fraud

A mock-location check in your app is a good first line of defence. It is also a check running on a device you do not control. On a rooted phone, a hooked process or a modified APK, the answer can be whatever the attacker wants it to be. If location drives money, payouts, attendance or game rewards, the final decision has to live on the server.

6,371 km
Mean Earth radius used by the haversine formula
API 18
Android added Location.isFromMockProvider()
iOS 14
Apple introduced App Attest (DCAppAttestService)

Treat the client as a witness, not a judge

The device is still your best source of information. It knows the raw coordinates, the reported accuracy, the fix timestamp and whether the OS thinks the fix came from a mock provider. The mistake is letting the device make the decision. Instead, have it send what it saw, and let the backend weigh that evidence alongside things the device cannot easily fake.

json
{
  "lat": 24.8607,
  "lng": 67.0011,
  "accuracyM": 12.4,
  "altitudeM": 18.2,
  "fixTime": 1759046400000,
  "clientTime": 1759046401250,
  "clientReportedMock": false,
  "integrityToken": "<opaque token from Play Integrity or App Attest>"
}
A location report the client sends with every sensitive action

Note clientReportedMock. The result of a library such as react-native-mock-location-detector is still useful, but as one signal among several, not as the verdict. A true is strong evidence. A false only means nothing was detected.

Impossible speed: the teleport check

Real people move at plausible speeds. Spoofed locations tend to jump: a driver who is in one district at 10:00 and across the country at 10:02. Store the last accepted fix per user or device, compute the great-circle distance to the new one, and divide by the time between them. The haversine formula is accurate enough for this.

ts
type Fix = { lat: number; lng: number; at: number }; // at = epoch ms

const EARTH_RADIUS_KM = 6371;
const toRad = (deg: number) => (deg * Math.PI) / 180;

export function distanceKm(a: Fix, b: Fix): number {
  const dLat = toRad(b.lat - a.lat);
  const dLng = toRad(b.lng - a.lng);
  const h =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(a.lat)) * Math.cos(toRad(b.lat)) * Math.sin(dLng / 2) ** 2;
  return 2 * EARTH_RADIUS_KM * Math.asin(Math.sqrt(h));
}

export function impliedSpeedKmh(prev: Fix, next: Fix): number {
  const hours = (next.at - prev.at) / 3_600_000;
  if (hours <= 0) return Infinity;
  return distanceKm(prev, next) / hours;
}
Haversine distance and implied speed between two fixes

Pick the threshold per product. A delivery rider on a scooter and a passenger on a plane have very different ceilings. Also allow for GPS jitter: two fixes a few seconds apart with 30 m accuracy can imply a silly speed over a tiny distance, so ignore pairs where the distance is smaller than the combined accuracy radius.

Time, accuracy and patterns that look too clean

  • —Clock skew. Compare clientTime with the server's receive time. Large skew suggests a tampered clock, which also breaks your speed maths.
  • —Stale fixes. A fixTime far older than the request means the app is replaying an old position. Reject or re-request a fresh fix.
  • —Suspiciously perfect data. Real GPS wanders. Identical coordinates across many reports, a constant accuracy value, or an altitude that never changes are common signs of a spoofing tool.
  • —Accuracy that does not match the context. A claimed 3 m accuracy indoors, repeatedly, deserves a second look.

Device integrity signals

Most location spoofing on Android that defeats the OS mock flag requires a modified environment. Platform attestation helps you notice that. On Android, the Google Play Integrity API lets your backend verify a signed verdict about whether the request comes from your genuine app binary on a device that passes integrity checks. On iOS, App Attest lets your server confirm that requests come from a legitimate instance of your app.

Always verify these tokens on the server, never in the app, and treat the result as another input to your risk decision. They reduce fraud from tampered apps and rooted devices, but they are not location proofs: an attested app on a genuine device can still receive a spoofed fix if the user enables a mock provider, which is exactly what the client-side check catches. The layers complement each other.

Combine signals into a risk score

No single signal is reliable enough to punish users on. Combine them into a score, keep the reasons, and map score ranges to actions. The weights below are illustrative: tune them against your own labelled cases.

ts
type Signals = {
  clientReportedMock: boolean;
  impliedSpeedKmh: number;
  clockSkewMs: number;
  fixAgeMs: number;
  identicalRecentFixes: number;
  integrityOk: boolean | null; // null = no verdict available
};

type Action = 'allow' | 'review' | 'block';

export function locationRisk(s: Signals, maxSpeedKmh = 200) {
  let score = 0;
  const reasons: string[] = [];
  const add = (points: number, reason: string) => {
    score += points;
    reasons.push(reason);
  };

  if (s.clientReportedMock) add(40, 'client reported mock provider');
  if (s.impliedSpeedKmh > maxSpeedKmh) add(35, 'impossible speed');
  if (Math.abs(s.clockSkewMs) > 5 * 60_000) add(15, 'clock skew');
  if (s.fixAgeMs > 2 * 60_000) add(10, 'stale fix');
  if (s.identicalRecentFixes >= 10) add(15, 'identical repeated fixes');
  if (s.integrityOk === false) add(30, 'failed integrity check');

  const action: Action = score >= 70 ? 'block' : score >= 30 ? 'review' : 'allow';
  return { score, reasons, action };
}
Illustrative weights; calibrate on real data

Prefer soft responses first: ask for a fresh fix, require the user to disable the mock app, hold a payout for review, or exclude the event from rewards. Reserve hard blocks for high scores or repeated offences, and give a human reviewer the stored reasons so decisions can be explained and appealed.

Detection on the device, decisions on the server.

Anti-fraud does not override privacy obligations. Tell users why you collect location, collect it only around the actions that need it, keep raw fixes for as short a time as your fraud process allows, and document the signals you use. If you operate in regions with data-protection law, your risk scoring is processing personal data and should be covered by your privacy notice.

Key takeaways

  • —Client-side mock detection is valuable but bypassable, so treat its result as evidence, not a verdict.
  • —Validate every sensitive location on the server with implied speed, clock skew and fix age.
  • —Look for data that is too clean: repeated identical coordinates, constant accuracy or altitude.
  • —Use Play Integrity and App Attest verdicts, verified server-side, as additional signals.
  • —Combine signals into a score with stored reasons, and prefer soft responses before hard blocks.

The server can only weigh the signals the app sends. For the client half of this setup, detecting mock providers in React Native, handling permissions and gating the UI, read Detecting Fake GPS in React Native.

Keep reading
(10)Let's talk

Ready to scaleyour application?

Stop worrying about technical debt. I can help you build a reliable, high-performance product from the ground up, tailored exactly to your business needs.

Actively open for new opportunities
Get in touch
Local time
--:-- PKT
© 2026 Syed Wajeeh Haider. All rights reserved.