Mobile fraud teams keep rediscovering the same hard truth: there is no reliable, permanent device identifier that a React Native app can just read. The identifiers the platforms expose are deliberately unstable — they reset, rotate, or require permissions precisely because permanent hardware IDs are a privacy hazard. So the “get the device ID” line in your fraud spec turns out to be a real engineering problem, not an API call.
This article covers why the obvious approaches fail, how to build a device identity in React Native that survives reinstalls and resists spoofing, and why the authoritative resolution has to happen on your server. It applies the general principles of mobile device fingerprinting to the specific realities of a cross-platform React Native app.
Why platform identifiers reset
The naive plan is to read a device identifier from the OS and store it. Every path there has a catch.
- iOS
identifierForVendoris stable only while at least one app from the same vendor stays installed. Uninstall the last one and it regenerates. It is also scoped per vendor, so it is not a global device ID. - Android IDs rotate across factory resets and, depending on the identifier, per app or per user profile. The advertising ID is user-resettable by design and gated by consent.
- A self-generated UUID in storage clears on reinstall, on app-data wipe, and whenever a fraudster wants a fresh identity. It is fully client-controlled, which makes it useless as a trust signal.
- Advertising identifiers require consent, can be zeroed out, and are meant for ads, not fraud.
The pattern is consistent: anything the OS hands you cheaply is either resettable or scoped in a way that breaks continuity. And anything you store yourself is under the attacker’s control. A stored value tells you “same install,” never “same device.” For fraud you need “same device,” which forces a different approach.
Combining signals into a device identity
A durable device ID comes from combining many stable-ish attributes into a fingerprint, then resolving that fingerprint to a persistent identity. No single attribute is reliable; together they are.
Useful signal categories in React Native:
- Hardware and OS characteristics: model, screen metrics, CPU/architecture, OS version, locale, timezone. Individually low-entropy, collectively distinguishing.
- Platform attestation: App Attest on iOS and Play Integrity on Android verify the app is genuine and the device is not obviously compromised. These are the strongest anchors you have.
- Integrity flags: jailbreak, root, and emulator detection, plus Frida hooking checks, because a tampered device produces untrustworthy signals.
- A keychain/keystore-backed secret that survives app reinstall on many devices, used as one input rather than the whole identity.
Client (React Native native module)
collect: hardware + OS + attestation token + integrity flags
│
▼
Server
verify attestation ── resolve fingerprint ──▶ stable visitor ID + confidence
The keychain-backed secret matters because, unlike app storage, keychain and keystore entries can persist across reinstalls on many configurations, giving you continuity a plain UUID never has. But treat it as one weighted input; do not trust it alone, since it can still be cleared.
Resolve identity on the server, not the client
The single most important architectural rule: the client collects signals, the server decides the identity. Anything computed and trusted on the device is spoofable, because the device belongs to the person you may be trying to catch.
Why server-side resolution is non-negotiable:
- A client-computed ID can be intercepted and replayed. A repackaged app can emit any value it likes.
- Attestation tokens must be verified server-side against the platform’s servers; a client cannot validate its own attestation.
- The mapping from fingerprint to persistent identity needs history the client does not have — prior sightings, confidence, associated accounts.
This is the same client-collects, server-decides split behind Node server-side verification and server-side bot detection. The React Native layer gathers evidence through a native module; the server verifies attestation, resolves the fingerprint to a visitor ID, and returns a confidence score. Your app then acts on the server’s verdict, never on a locally computed ID.
Handling both platforms cleanly
React Native’s promise is one codebase; device identity is where the platform seams show. iOS and Android expose different identifiers, different attestation systems, and different integrity APIs. A clean design isolates the differences behind one interface.
| Concern | iOS | Android |
|---|---|---|
| Vendor identifier | identifierForVendor | app-scoped ID |
| Attestation | App Attest / DeviceCheck | Play Integrity |
| Persistent secret store | Keychain | Keystore |
| Root/jailbreak surface | jailbreak checks | root checks |
Wrap these in a native module that exposes a single collectSignals() to JavaScript, returning a normalized payload. The JavaScript layer never branches on platform; it sends the payload up and consumes the server’s device ID. This keeps the fraud logic platform-agnostic while the messy platform specifics stay in native code. See React Native device ID guidance in the SDK docs for the concrete module shape, and Flutter fraud detection for the equivalent pattern in that framework.
Using the device ID in fraud flows
Once you have a stable, server-resolved device ID with a confidence score, it plugs into the same flows as web device intelligence:
- New-device login checks at authentication. A high-value action from a never-seen device warrants step-up. See new device login detection.
- Multi-accounting and device farms, where one physical device spawns many accounts. See multi-accounting detection and device farm detection.
- Payment and promo abuse, where device continuity links a “new” user to a banned one. See payment fraud device signals and promo abuse prevention.
- Rate limiting by device rather than by IP, which mobile networks make unreliable. See rate limiting by device.
Feed the device ID and integrity flags into a suspect score so the decision is explainable and tunable against your false-positive budget.
Frequently asked questions
Why not just use a UUID stored on device?
A stored UUID resets on reinstall, app data clears, and device migration, and it is trivial for a fraudster to wipe. It works for basic analytics but fails as a fraud signal because it is fully under the client’s control.
Is there one identifier that is stable across iOS and Android?
No single OS identifier is both stable and available across both platforms with the guarantees fraud needs. A durable device ID comes from combining platform attestation with device signals and resolving them server-side.
Should the device ID be computed on the client or the server?
Collect signals on the client, resolve the identity on the server. Anything the client alone decides can be spoofed, so the authoritative ID must be produced where the attacker cannot rewrite it.
Stable device identity in React Native is not a field you read; it is a system you build. Gather many signals through a native module, anchor them with platform attestation and integrity checks, and resolve the authoritative ID on your server where it cannot be rewritten. Do that, and “same device” becomes a signal you can actually trust across reinstalls and across both platforms. Start with the SDKs and the docs.
Run it yourself
Prynt is open-source, self-hostable device intelligence — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics you own end to end.