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

推荐订阅源

云风的 BLOG
云风的 BLOG
P
Privacy International News Feed
Vercel News
Vercel News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 叶小钗
F
Fortinet All Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 最新话题
AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta
Attack and Defense Labs
Attack and Defense Labs
Recent Announcements
Recent Announcements
Recent Commits to openclaw:main
Recent Commits to openclaw:main
PCI Perspectives
PCI Perspectives
Cloudbric
Cloudbric
AI
AI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
IT之家
IT之家
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
J
Java Code Geeks
M
MIT News - Artificial intelligence
Cisco Talos Blog
Cisco Talos Blog
V2EX - 技术
V2EX - 技术
Webroot Blog
Webroot Blog
Microsoft Security Blog
Microsoft Security Blog
Cyberwarzone
Cyberwarzone
博客园 - 聂微东
G
Google Developers Blog
W
WeLiveSecurity
罗磊的独立博客
P
Privacy & Cybersecurity Law Blog
阮一峰的网络日志
阮一峰的网络日志
A
About on SuperTechFans
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
T
Tailwind CSS Blog
V
Visual Studio Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
S
Secure Thoughts
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
Google DeepMind News
Google DeepMind News
雷峰网
雷峰网
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
Blog — PlanetScale
Blog — PlanetScale
The Last Watchdog
The Last Watchdog
P
Proofpoint News Feed

Giant Robots Smashing Into Other Giant Robots

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 The Bike Shed Ep 505: What is a “principal” or “staff” engineer? Your vibe coded website is going to get you fined Roux’s New Component Library Why we're choosing stewardship over an exit The Bike Shed Ep 503: Seeing the Graph for the Trees Announcing Shoulda Matchers 8.0: validate multiple attributes in one line AI's "overnight" solution for our flaky tests took two weeks to adopt The Playwright debugging tool Rails devs aren't using Meet thoughtbot at Brighton Ruby 2026 614: AI Code Audits The mistake I didn't realise I was making when designing workshops AI crawlers are inflating your view counts 502: Apps That Make Our Work Go Toast: the 2-minute test that reveals how you think about building products How I Built a Chrome Extension Wrapper (and Everything That Tried to Stop Me) Enforcing Your Ruby Style Guide on AI-Generated Code Copy as Markdown: AI-friendly blog posts 501: What makes for good technical writing? The Four Signals of AI Observability Can you really launch a tech business with a no-code app builder? 612: Do fish drink? This week in #dev (May 15, 2026) Lost, forgotten, and unfamiliar HTML 500: Celebrating with past hosts Why Duck Typer?
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.