惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
M
MIT News - Artificial intelligence
博客园_首页
IT之家
IT之家
L
LangChain Blog
D
DataBreaches.Net
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
人人都是产品经理
人人都是产品经理
博客园 - Franky
云风的 BLOG
云风的 BLOG
罗磊的独立博客
量子位
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 【当耐特】
博客园 - 叶小钗
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
B
Blog
T
Tailwind CSS Blog
A
About on SuperTechFans
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

Giant Robots Smashing Into Other Giant Robots

Client success starts before kickoff 5 easy, actionable tips for software development in healthcare Announcing importmap-update: automated dependency updates for importmap-rails When to vibe code an app and when to hire someone Why leaders have to know about PMS 🩸 Is AI ruining my brain? Tech Leaders Meetup is coming to Edinburgh Designers already think in React Tech Leaders Meetups are back in London this autumn GPT and Claude go to heraldry school Inserting State Transitions in Postgres Don’t hire thoughtbot to write code AI makes creating software faster, but in regulated industries, judgment matters more Tech Leaders meetup in Amsterdam PMs Don't Need to Code, but They Do Need to Understand How healthcare tech teams innovate while balancing speed and security Can’t touch the DOM? Reach for :has() to style any element Buying Time, Choosing Words: Consulting Through Diplomatic Communication thoughtbot around the world, meet us at upcoming events Modeling State Transitions in Postgres Humid 1.0: React server-side rendering in Rails can be easy! A prototype is not a product. It's a conversation. New: The State of Software Delivery in Healthcare Sign in with Google for React Native What founders told us about working with AI tools for startups Join us: Building Secure Healthcare Systems Upcase has retired, but the learning continues The Bike Shed Ep 506: The Muppet Software Team Migrating to native stack navigation, with a surprise from iOS 26 Past and present thoughtbotters at LRUG this Monday
Biometrics authentication for your mobile app
Rakesh Arunachalam · 2026-05-25 · via Giant Robots Smashing Into Other Giant Robots

Biometrics (Face ID, Fingerprint and more) authentication have quickly become something users expect from a modern mobile app. These are fast, familiar, and removes friction that comes with typing passwords on the phone. With React Native, biometrics authentication can be an easy feature or a tricky one depending on the options you take. There are multiple ways to build it, and they do not all offer the same level of security.

In this blog post, I’m going to share three approaches that we evaluated for a recent project. All three approaches can enable a smooth authentication experience, but they come with different tradeoffs in terms of implementation complexity, user experience, and security implications. If you are building this feature for a banking/financial services app, a healthcare app, or a product handling sensitive user data, these tradeoffs matter a lot. In this post, for each approach of biometrics authentication, we’ll explore how they work, where they fall short, and when each makes sense.

Simple Prompt (Approach 1)

The simplest way to add biometrics to your app is to use it as a UI level gate:

  • Prompt the user to authenticate with their biometrics (face or fingerprint).
  • On success, read the auth token from secure storage

This is what simplePrompt in react-native-biometrics does. It triggers the native biometric dialog and returns a boolean indicating success or failure. The critical thing to understand about this approach is that the biometric check and the token retrieval are two separate, unlinked operations. The app calls simplePrompt, gets back true, then independently reads the token from storage. There is no cryptographic connection between them and the biometric result is merely a signal to our code, not a hardware enforced gate on the data itself.

// Pseudocode showing how this works
const success = await biometrics.prompt('Confirm your identity')

if (success) {
  const token = await secureStorage.read('auth_token')
  // log user in
}

Expo vs React Native CLI (Approach 1)

With Expo managed workflow, you’d reach for expo-local-authentication rather than react-native-biometrics. The API is nearly identical, returns a result object with a success boolean, and it works without ejecting or adding any native configuration. To store and retrieve the auth token securely, pair it with expo-secure-store, which writes to the iOS Keychain and Android Keystore under the hood.

With React Native CLI (bare workflow), react-native-biometrics is the natural choice for the biometric prompt. For token storage, the equivalent is react-native-keychain, which gives you direct access to the iOS Keychain and Android Keystore and supports more granular security configuration than expo-secure-store.

Pros (Approach 1)

  • Easy to implement with minimal boilerplate and works out of the box on both platforms
  • Supports Face ID, Touch ID, and fingerprint without extra configuration
  • Works across iOS and Android with a consistent API
  • No backend required
  • Multi device support as each device independently manages its own stored token

Cons (Approach 1)

  • Biometric enforcement happens at the App level, not the OS or hardware level. The biometric check and the token read are two independent steps with no cryptographic coupling between them
  • On a jailbroken or rooted device, tools like Frida can be used to hook into the app at runtime, force simplePrompt to return true, and retrieve the token without the user actually authenticating
  • Does not meet FIDO2 or WebAuthn standards as there is no challenge-response, no server side verification, and no cryptographic proof that a real biometric event occurred on the device
  • The stored token is not hardware locked. Code that bypasses the biometric gate can read it directly from storage
  • Not appropriate for apps handling sensitive financial, healthcare, or regulated data

Cryptographic Key Pair (Approach 2)

This approach upgrades biometrics from a UI gate to something the server can independently verify. The createKeys() method in react-native-biometrics generates an asymmetric key pair. The device holds a private key locked behind biometrics and the server holds the matching public key. Login is just the device signing a challenge and the server verifying the signature.

What makes each piece do its job:

  • Private key — Created inside the device chip at enrollment and never exported. It is only unlocked by a passing biometric scan.
  • Public key — Returned to the app and registered on the server. It is used only to verify signatures.
  • Challenge — A one-time UUID issued by the server at login. The server enforces an expires_at field so late or replayed submissions are rejected.
  • Signature — The chip’s signed challenge sent to the server. The server checks it against the stored public key and issues an access token.

The server never learned your biometric, your private key, or even your password. It just confirmed the right chip signed the right challenge at the right time.

Enrollment

The device generates a key pair inside the hardware chip. The private key never leaves. The public key gets sent to the server.

Private key example: Stays on device, locked by biometrics, never transmitted.

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA2a2rwplBQLF29amygykEMmYz0+Ik9e8bKGFCNSGBaGMq
mMV5FBtLGMGMmFnMjQKFMkBHiNIEuAsYhVMqAC+XAAA...
-----END RSA PRIVATE KEY-----

Public key example: Sent to server at enrollment, stored against the user’s account.

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2a2rwplBQLzamygyk
EMmYz0+Ik9e8bKGFCNSGBaGMqmMV5FBtLGJmFnMjQKF...
-----END PUBLIC KEY-----

Authentication

Step 1: User on the device says: “I want to log in.”

Step 2: Server generates a fresh challenge and sends it back:

{
  "challenge": "a3f8c2d1-9b4e-4f7a-8c3d-2e1f0a9b8c7d",
  "expires_at": "2026-05-01T10:00:30Z"
}

Just a random UUID. Expires in 30 seconds, useless after that.

Step 3: Device prompts Face ID / fingerprint.

Biometric passes → chip unlocks private key → signs the challenge:

{
  "challenge": "a3f8c2d1-9b4e-4f7a-8c3d-2e1f0a9b8c7d",
  "signature": "XrAGWHSPpnmqzQxL3kVt9Yw2NcBdEfUoRiMaTlKsPqJhGvCxZnWbOuIyDe..."
}

This is sent over the network. No password or token or private key. Just the challenge and its signature.

Step 4: Server verifies:

verify(
  publicKey  = <the one stored at enrollment>,
  challenge  = "a3f8c2d1-9b4e-4f7a-8c3d-2e1f0a9b8c7d",
  signature  = "XrAGWHSP..."
) → ✅ true

Server responds:

{
  "access_token": "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyJ9...",
  "expires_in": 720 // 12 hours
}

Expo vs React Native CLI (Approach 2)

expo-local-authentication implements the simple prompt pattern from Approach 1 and exposes no key management APIs. There is no createKeys() or createSignature() equivalent in the Expo SDK, so you cannot implement the cryptographic flow with the standard managed workflow or Expo Go.

With React Native CLI, react-native-biometrics links natively on install and gives you direct access to createKeys(), createSignature(), and deleteKeys(). Your server needs three endpoints: one to store the public key at enrollment, one to issue a challenge, and one to verify the signature and return an access token.

Pros (Approach 2)

  • The biometric check and authentication are cryptographically coupled: a valid signature can only be produced if the biometric passes, enforced by the hardware chip, not App code.
  • Protects against replay attacks: every challenge is a one-time UUID with a short expiry window.
  • Closest to FIDO2 of the three approaches with hardware backed keys, biometric gated signing, and server-side challenge-response compared to the other approaches.
  • Resistant to runtime hooking tools like Frida as any spoofed signature here fails server-side verification because producing a valid signature requires the private key, which never leaves the hardware chip.

Cons (Approach 2)

  • More complex to implement than Approach 1 as it requires backend work for key enrollment, challenge-response, and server-side verification.
  • Multi device support is slightly complex as it requires the server to manage, store, and revoke a separate public key for every enrolled device per user.
  • expo-local-authentication does not support this flow; Expo users need a bare React Native workflow, which adds build infrastructure overhead compared to the other two approaches.

Keychain (Approach 3)

This approach sits between the previous two. Like Approach 1, the auth token lives on the device. Like Approach 2, the biometric check is enforced by the OS at the hardware level, not by the React Native JS code.

When the token is written to the iOS Keychain or Android Keystore with a biometric access control flag, the OS will refuse to decrypt and return the value unless a successful biometric scan happens. The biometric prompt and the token decryption become a single OS-level operation. The App does not call a separate prompt API and then read the value, it just reads the value, and the OS shows the biometric prompt as part of completing that read.

// Pseudocode showing how this works
const token = await secureStorage.read('auth_token', {
  requireAuthentication: true,
})
// log user in

In practice, most apps store only a refresh token behind this gate and keep the access token in memory. The refresh token is long lived but never leaves the device, while the access token is short lived (~12 hours), lives in memory only, and is obtained by exchanging the refresh token at the start of each session. If an access token leaks, it expires quickly on its own. The refresh token, the more powerful credential, is hardware protected and never transmitted after the initial login.

Expo vs React Native CLI (Approach 3)

With Expo, expo-secure-store implements this pattern via requireAuthentication: true. The biometric prompt fires automatically on read, and SecureStore.canUseBiometricAuthentication() lets you check up front whether the device has biometrics enrolled before opting the user into biometric sign in. No native modules to install, and it works inside the managed workflow.

With React Native CLI, react-native-keychain gives the same OS enforced gate but with more granular controls. You pass accessControl: ACCESS_CONTROL.BIOMETRY_CURRENT_SET and securityLevel: SECURITY_LEVEL.SECURE_HARDWARE to setGenericPassword, which guarantees the key is stored securely.

Pros (Approach 3)

  • Biometric enforcement happens at the OS/hardware level, not the App level. A patched App using tools like Frida that bypasses the biometric call still cannot read the token because the OS will not release the encryption key.
  • No backend work required, unlike Approach 2. Only the token storage layer changes, the rest of the auth stack stays the same.
  • Works inside the Expo managed workflow via expo-secure-store, so we don’t need a Bare workflow for this to be implemented.
  • Multi device support is easy. Each device stores its own copy of the token encrypted securely.
  • BIOMETRY_CURRENT_SET invalidates the stored token automatically when a new biometric is enrolled, which is a sensible default for apps handling sensitive data.

Cons (Approach 3)

  • The token still lives on the device. If the access token is compromised through a network breach or server side leak, biometrics on the device cannot help. This can be mitigated by using a refresh token.
  • Token expiry and rotation has to be handled in the App, typically by pairing this with a refresh token pattern explained above.
  • There is no server side proof that biometrics were used. The server sees a normal token request and trusts the client. Approach 2 is the only one of the three that gives the server, cryptographic evidence of a real biometric event.
  • Does not meet FIDO2 or WebAuthn standards as there is no challenge-response and no asymmetric key pair, just hardware backed symmetric storage.

Conclusion

Approach 1 Approach 2 Approach 3
Biometric enforcement App (JS) Hardware chip OS / hardware
Frida / patched app bypass Vulnerable Resistant Resistant
Backend required No Yes No
Server-side biometric proof No Yes No
Replay attack protection No Yes No
Multi-device support Easy Complex Easy
Expo managed workflow Yes No Yes
FIDO2 / WebAuthn alignment No Closest No

If your app is relatively simple (a productivity tool, social app or utility), and biometrics is primarily a convenience feature for a fast login, Approach 1 is a reasonable choice. The implementation is straightforward and the tradeoffs are acceptable when there is no sensitive financial or personal data at stake.

If you are building a banking app, a financial services product, a healthcare app, or anything that handles regulated personal data, the bar is higher. Approach 1 is not appropriate here. Between Approaches 2 and 3, the deciding factor is usually team capability and backend access. Approach 3 requires no backend changes, making it the practical default for most teams. Approach 2 is the strongest option but requires a backend. If your team has the backend access and is comfortable with the added complexity, Approach 2 is the right call for maximum security for the mobile app.

In practice, most apps in sensitive industries start with Approach 3 and move toward Approach 2 if compliance requirements or a security audit demands server side verification.