DEV Community

FindIP
FindIP

Posted on

App Attest and Play Integrity won't catch a real phone behind a proxy

App Attest and Play Integrity answer one question: is this my genuine app on a genuine device? That's worth knowing, and you should use them. But a real, unmodified app on a real phone passes, whether that phone is on a home connection or behind a proxy in a rack of phones.

So if you're fighting fake signups, bonus farming or account takeover, you need a second question: what network is this request coming from?

Why not check on the device?

You can look for a VPN from inside the app. On Android, NetworkCapabilities.hasTransport(TRANSPORT_VPN) tells you about a VPN on the device. It's fine for showing a message, but it won't help against fraud, for two reasons:

  • The app is reporting on itself. A modified client can answer whatever it likes.
  • It can't see a proxy further upstream.

The check belongs on a server, which sees the IP the request actually came from.

What to look at

  • Network type. Hosting and datacenter networks are rarely a phone on a carrier or home connection.
  • Known exits. VPN exit nodes, open proxies and Tor exits.
  • Changes in a session. The IP, network or country switching mid-session.

None of this is proof. iCloud Private Relay, corporate VPNs and carrier NAT are all normal for honest users. So treat the result as risk: let most people through, and ask for an extra step when it's high.

How the pieces fit

Network scoring App Attest Play Integrity
Answers Is the network behind this action risky? Is this my genuine app on a genuine Apple device? Is this my genuine app, from Google Play, on a certified device?
Misses Whether the app is genuine The network The network
Decided by Your backend Your backend Your backend

Run them side by side. The rule for all three is the same: your backend decides, never the app.

With FindIP Shield

Disclosure: we build this. FindIP Shield does the network part, and it's free during the preview. The SDKs are new, so no production apps use them yet.

iOS. Add the Swift package (iOS 15+, no dependencies):

.package(url: "https://github.com/findip-net/findip-shield-swift", from: "1.0.3")
Enter fullscreen mode Exit fullscreen mode

Then, on the login button:

let verdict = try await FindIPShield.track(ShieldEvents.loginAttempt, screen: "login")
// Send verdict.sessionId with the login request; your backend verifies it.
Enter fullscreen mode Exit fullscreen mode

Android. Add the library (Android 8+, INTERNET permission only):

implementation("net.findip:shield-android:1.0.3")
Enter fullscreen mode Exit fullscreen mode

Then, inside a coroutine:

val verdict = FindIPShield.track(ShieldEvents.LOGIN_ATTEMPT, screen = "login")
// Send verdict.sessionId with the login request; your backend verifies it.
Enter fullscreen mode Exit fullscreen mode

Your backend then asks Shield about that session with the secret key and decides. The key in the app is public and proves nothing. See server verification.

Two details matter:

  • No answer means unknown, not safe. A timeout returns an unknown status, never allow. Decide per action what to do: a login can usually continue, while a signup might ask for an extra check.
  • Nothing the user typed is sent. No form values, advertising IDs, contacts, location or device fingerprint. The full list is in the docs.

What this won't catch

Network scoring doesn't recognize the same phone behind a new account, and residential proxies are hard for anyone, because they route through real home connections. Pair it with integrity checks and your own account-level limits.

Top comments (0)