Authsignal secures millions of passkey transactions out of our hosted Sydney region.

Authsignal secures millions of passkey transactions out of our hosted Sydney region.

Join us today!
Blog
/
Current article

Implementing Passkeys in React Native: Why Expo Go Falls Short and How to Fix It

Last Updated:
April 22, 2025
Ashutosh Bhadauriya
Implementing Passkeys in React Native: Why Expo Go Falls Short and How to Fix It
AWS Partner
Authsignal is an AWS-certified partner and has passed the Well-Architected Review Framework (WAFR) for its Cognito integration.
AWS Marketplace

Expo Go is a fantastic tool for React Native development, allowing you to quickly iterate on your app without the need for native builds. However, it comes with an important limitation:

Expo Go does not natively support passkeys due to its limitations in handling native code dependencies. Passkey authentication requires native modules that are not compatible with Expo Go's managed environment.

The core issue stems from the architecture of Expo Go:

  1. Sandboxed Environment: Expo Go runs your JavaScript code in a pre-built native container that has limited ability to incorporate custom native modules.
  2. Native Dependencies: Passkey implementation requires platform-specific native code to interact with the device's security features.
  3. Managed Workflow Limitations: In Expo's managed workflow, you can't directly modify the native code or add custom native modules without ejecting.

Options for Implementing Passkeys in React Native with Expo
Option 1: Use Development Builds (Recommended)

The most effective approach is to use Expo's development builds while staying within the Expo ecosystem:

# Install the necessary packages
npx expo install expo-dev-client

# Create a development build
eas build --profile development --platform all

With development builds, you can:

  • Add custom native modules via config plugins
  • Use Expo's managed workflow for most features
  • Test native functionality on real devices
Option 2: Use Config Plugins with EAS Build

Expo's config plugins system allows you to modify native code without ejecting:

  1. Create a config plugin for passkey implementation:
// plugins/withPasskeys.js
const withPasskeys = config => {
  // iOS modifications
  if (config.ios) {
    config.ios = {
      ...config.ios,
      // Add entitlements for passkeys
      entitlements: {
        ...(config.ios.entitlements || {}),
        'com.apple.developer.associated-domains': ['webcredentials:yourdomain.com'],
        'com.apple.developer.authentication-services.autofill-credential-provider': true
      }
    };
  }
  
  // Android modifications
  if (config.android) {
    // Add necessary Android configurations
  }
  
  return config;
};

module.exports = withPasskeys;

  1. Register the plugin in your app.json:
{
  "expo": {
    "plugins": [
      "./plugins/withPasskeys"
    ]
  }
}

Option 3: Use Expo's Prebuild to Generate a Native Project
expo prebuild

This command generates the necessary native code for your project, allowing you to directly modify native modules while still using Expo tools.

How to implement Passkeys in React Native using AuthSignal

We’ll be using Authsignal’s React Native SDK to add passkeys

Step 1: Install the SDK
# Install using yarn
yarn add react-native-authsignal

# Link native iOS dependencies
npx pod-install ios

Step 2: Configure Native Requirements

After you have configured your Relying Party on Authsignal Portal, you should follow the steps below.

For iOS:

  1. Host an apple-app-site-association file on your domain that matches your relying party:
GET https://<yourrelyingparty>/.well-known/apple-app-site-association

The response should contain:

{
   "applinks": {},
   "webcredentials": {
      "apps": ["ABCDE12345.com.example.app"]
   },
   "appclips": {}
}

Where ABCDE12345 is your team ID and com.example.app is your bundle identifier.

  1. In XCode under “Signing & Capabilities” add a webcredentials entry for your domain / relying party e.g. example.com:

For Android:

  1. Host an assetlinks.json file on your domain that matches your relying party:

The response JSON should look something like this:

[{
    "relation": [
        "delegate_permission/common.handle_all_urls",
        "delegate_permission/common.get_login_creds"
    ],
    "target": {
        "namespace": "android_app",
        "package_name": "com.example",
        "sha256_cert_fingerprints": [
            "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"
        ]
    }
}]

  1. Finally, you will need to add an expected origin value for your APK hash when configuring passkeys in the Authsignal Portal.

Step 3: Initialize the Authsignal Client

You can find your tenant ID in the Authsignal Portal.

import { Authsignal } from "react-native-authsignal";

const authsignal = new Authsignal({
  tenantID: "YOUR_TENANT_ID",
  baseURL: "YOUR_REGION_BASE_URL", // e.g., "https://api.authsignal.com/v1"
});

Step 4: Implement Passkey Registration
const handleCreatePasskey = async () => {
  try {
    // Get token from your backend (after user is authenticated)
    const { token } = await fetchTokenFromBackend();
    
    // Register passkey
    const response = await authsignal.passkey.signUp({
      token: token,
      username: "user@example.com",
      displayName: "User Name",
    });
    
    if (response.success) {
      console.log("Passkey created successfully");
    }
  } catch (error) {
    console.error("Error creating passkey:", error);
  }
};

Step 5: Implement Passkey Authentication
const handleSignInWithPasskey = async () => {
  try {
    const response = await authsignal.passkey.signIn({ 
      action: "signInWithPasskey" 
    });
    
    if (response.data?.token) {
      // Send token to your server to validate
      const validationResult = await validateTokenWithBackend(response.data.token);
      
      if (validationResult.success) {
        // User is authenticated
        console.log("Successfully authenticated with passkey");
      }
    }
  } catch (error) {
    console.error("Error signing in with passkey:", error);
  }
};

Note: On your backend, you'll need to validate the Authsignal token using the Authsignal Server SDK. This verifies that the passkey authentication was successful and hasn't been tampered with. For implementation details, see Authsignal's backend validation documentation.

That’s it, you’ve successfully added passkeys to your React Native Application.

Conclusion

By using development builds with Authsignal's SDK, you can implement secure passkey authentication while staying in the Expo ecosystem.

The initial setup requires more effort than using Expo Go, but the security and UX benefits for your users make it worthwhile. Passkeys eliminate password-related vulnerabilities while providing a seamless authentication experience. If passkeys are important for your app, the transition from Expo Go is a necessary and valuable investment.

Try out our passkey demo
Passkey Demo
Have a question?
Talk to an expert
You might also like
How to Add Push Authentication in Your React Native App With Authsignal
Learn how to implement push authentication in your React Native app using Authsignal. Replace passwords with secure one-tap verification for simpler, stronger user authentication.
Embedding Passkeys into Your App Workflows: Why Authsignal's Approach Stands Out
Discover how to embed passkeys into your app workflows. Learn why Authsignal’s non-IdP approach enables seamless, risk-based authentication without the need for identity system overhauls—saving time and reducing friction.
Twilio Verify vs Authsignal: The Twilio Alternative That Does More With Less Dev Effort
Looking for a Twilio Verify alternative? Discover how Authsignal provides a complete step-up authentication solution with passkeys, adaptive MFA, and powerful no-code policy tools—all with minimal engineering effort.

Secure your customers’ accounts today with Authsignal.