# Detecting Fake GPS in React Native

> Use react-native-mock-location-detector to catch mock-location apps on Android, gate your UI, and know when to move to the maintained fork.

- Author: Syed Wajeeh Haider
- Published: Sep 28, 2026
- Category: React Native
- Tags: React Native, Android, Location, Security
- Reading time: 5 min
- URL: https://syedwajeehhaider.com/detect-fake-gps-react-native

If your app pays drivers per trip, checks employees in by location, or rewards players for visiting places, someone will eventually try a fake GPS app. On Android, mock-location apps are a few taps away in Developer options. The good news is that Android marks fixes coming from a mock provider, and a small React Native package exposes that flag to JavaScript.

- **1 method**: checkMockLocationProvider(), Android only
- **API 31**: Android 12: isMock() replaces the deprecated isFromMockProvider()
- **iOS 15**: CLLocationSourceInformation.isSimulatedBySoftware added

## How the detector works

The package `react-native-mock-location-detector` (latest published version 2.0.0) is a thin native module. Its default export, `RNMockLocationDetector`, has exactly one method: `checkMockLocationProvider()`, which returns a `Promise<boolean>`. The README mentions passing a dialog title, text and button label, but the actual code takes **no arguments** and shows no dialog. Any UI is yours to build.

On the native side it asks Google Play services' fused location provider for the **last known location** via `FusedLocationProviderClient.getLastLocation()`, then calls `Location.isFromMockProvider()` on it (available since API 18). On very old devices it falls back to the `Settings.Secure.ALLOW_MOCK_LOCATION` setting.

> **false means "not detected", not "real":** The method resolves `false` when location permission has not been granted and when there is no last known location. So a `false` can simply mean the check could not run. Always request permission first, and run the check after your app has received at least one location fix so the last known location exists and is recent.

## Install and link

Modern React Native autolinks native modules, so there is no `react-native link` step. You do need a native rebuild, and the device needs Google Play services because the check uses the fused provider. The package's own manifest already declares `ACCESS_COARSE_LOCATION` and `ACCESS_FINE_LOCATION`.

```bash
npm install react-native-mock-location-detector

# rebuild the Android app so the native module is compiled in
npx react-native run-android
```

## A useMockLocationGuard hook

Wrap the permission request and the check in a hook that exposes a clear state. Because the package is Android only, guard with `Platform.OS` so iOS builds never call a module that does not exist. Re-check whenever the app returns to the foreground with `AppState`: users often switch to their spoofing app and back.

*useMockLocationGuard.ts*

```ts
import { useCallback, useEffect, useState } from 'react';
import { AppState, PermissionsAndroid, Platform } from 'react-native';
import RNMockLocationDetector from 'react-native-mock-location-detector';

export type GuardState =
  | 'checking'
  | 'ok'
  | 'mocked'
  | 'no-permission'
  | 'unsupported';

export function useMockLocationGuard() {
  const [state, setState] = useState<GuardState>('checking');

  const check = useCallback(async () => {
    if (Platform.OS !== 'android') {
      setState('unsupported');
      return;
    }

    const result = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
    );
    if (result !== PermissionsAndroid.RESULTS.GRANTED) {
      setState('no-permission');
      return;
    }

    const mocked = await RNMockLocationDetector.checkMockLocationProvider();
    setState(mocked ? 'mocked' : 'ok');
  }, []);

  useEffect(() => {
    check();
    const sub = AppState.addEventListener('change', (next) => {
      if (next === 'active') check();
    });
    return () => sub.remove();
  }, [check]);

  return { state, recheck: check };
}
```

`PermissionsAndroid.request` returns immediately with `GRANTED` if the user already allowed location, so calling it on every check is cheap. If the user picked "Don't allow" permanently, it returns `NEVER_ASK_AGAIN`, and your `no-permission` screen should explain how to enable it in system settings.

## Gate the experience, kindly

When a mock provider is detected, block only the location-sensitive flows and explain what to do. Most users who trip the check are developers or testers who forgot a mock app was enabled, not fraudsters. The exact path to Developer options differs between manufacturers, so describe it in general terms rather than promising a specific screen.

*MockLocationGate.tsx*

```tsx
import type { ReactNode } from 'react';
import { ActivityIndicator, Button, Text, View } from 'react-native';
import { useMockLocationGuard } from './useMockLocationGuard';

export function MockLocationGate({ children }: { children: ReactNode }) {
  const { state, recheck } = useMockLocationGuard();

  if (state === 'checking') {
    return <ActivityIndicator style={{ flex: 1 }} />;
  }

  if (state === 'mocked') {
    return (
      <View style={{ flex: 1, justifyContent: 'center', padding: 24, gap: 12 }}>
        <Text style={{ fontSize: 22, fontWeight: '600' }}>Turn off mock location</Text>
        <Text>
          A mock location app is active on this device. Disable it in
          Developer options (or uninstall it), then come back.
        </Text>
        <Button title="I've turned it off" onPress={recheck} />
      </View>
    );
  }

  if (state === 'no-permission') {
    return (
      <View style={{ flex: 1, justifyContent: 'center', padding: 24, gap: 12 }}>
        <Text>Location access is needed to continue.</Text>
        <Button title="Try again" onPress={recheck} />
      </View>
    );
  }

  return <>{children}</>;
}
```

## Moving to the maintained fork for iOS

The original package's README states that the author no longer maintains it, and recommends `react-native-turbo-mock-location-detector` by jpudysz. The fork supports both the old and new React Native architectures and adds iOS. On Android it uses `location.isMock` on API 31 and above, and `isFromMockProvider` below that. On iOS 15 and above it reads `CLLocationSourceInformation.isSimulatedBySoftware`.

*Import from react-native-turbo-mock-location-detector (its README example shows the old package name)*

```ts
import {
  isMockingLocation,
  MockLocationDetectorErrorCode,
  type MockLocationDetectorError,
} from 'react-native-turbo-mock-location-detector';

export type MockCheck = 'ok' | 'mocked' | 'gps-off' | 'no-permission' | 'unknown';

export async function checkMockLocation(): Promise<MockCheck> {
  try {
    const { isLocationMocked } = await isMockingLocation();
    return isLocationMocked ? 'mocked' : 'ok';
  } catch (e) {
    const error = e as MockLocationDetectorError;
    switch (error.code) {
      case MockLocationDetectorErrorCode.GPSNotEnabled:
        return 'gps-off';
      case MockLocationDetectorErrorCode.NoLocationPermissionEnabled:
        return 'no-permission';
      case MockLocationDetectorErrorCode.CantDetermine:
      default:
        return 'unknown'; // always the case on iOS below 15
    }
  }
}
```

Unlike the original, the fork rejects with a `{ code, message }` error instead of quietly returning `false`, which makes the "could not check" case explicit. Run `pod install` in the `ios` folder after installing, and note that it will not work in Expo Go: Expo projects need a development build that includes the native module.

> **Client checks can be bypassed:** On a rooted device, with a hooking framework or a repackaged APK, an attacker can make any of these checks return whatever they like. Use the client result to improve the experience and as one signal, and validate sensitive locations on the server too.

## Key takeaways

- `react-native-mock-location-detector` exposes one Android-only method, `checkMockLocationProvider()`, which takes no arguments.
- It inspects the last known fused location, so request permission and get a fix first; `false` only means nothing was detected.
- Wrap it in a hook, guard with `Platform.OS`, and re-check when the app returns to the foreground.
- Block only location-sensitive flows and explain how to turn the mock app off.
- For iOS 15+, the new architecture and explicit error codes, use `react-native-turbo-mock-location-detector`.

Detection on the device is only half the story. Next, read Why Client-Side Fake GPS Checks Aren't Enough to see how to validate location on the server with speed, time and integrity signals.
