Setting Up SSL Pinning into React Native

Setting Up SSL Pinning into React Native

Hello, my name is Luiz and in this tutorial i'll teach you how to add a new layer of protection on the mobile side that will defend your app from Man in the Middle Attacks. So without any further ado lets get started!

Why SSL Pinning is Needed?

Let’s say you’re connecting to your bank’s website or app. Generally, these apps rely on a system of Certificate Authorities (CAs)—big, trusted organizations that issue digital certificates. If your bank’s website or app presents a valid certificate issued by a recognized authority, your device says, “Yep, this checks out,” and sets up a secure (SSL/TLS) connection.

This works nicely most of the time. But there’s a weak spot: if somebody manages to create a fraudulent certificate that still looks valid to your device (for example, a certificate from a compromised or malicious CA), they can sneak in as a man-in-the-middle (MITM) using a proxy between your app and the server, then the attacker can then intercept your data, read it, or even alter it before sending to the server.

What is SSL Pinning

That’s where SSL Pinning (also called certificate pinning or public key pinning) steps in to save the day. Think of it as an extra piece of knowledge your app has—a secret handshake, if you will, that ensures it’s talking to the right server and nobody else.

Imagine you meet up with a close friend at a crowded venue. Sure, you could rely on them showing an official ID, but maybe you also have a special password or secret handshake you worked out in advance. If they don’t know that handshake, they’re not your friend—end of story.

Similarly, with SSL Pinning, your app “knows” exactly what the server’s certificate or public key looks like, because it’s stored—or “pinned”—in the app’s code. When your app tries to connect, it compares the server’s certificate to that pinned one. If they don’t match exactly, the app refuses to connect. This means even if a hacker has a seemingly legitimate certificate, it won’t fool the pinned check.

Setting up Pinning into Your React Native App

Securing your React Native app with SSL pinning can help protect against man-in-the-middle (MITM) attacks. Below, I’ll walk you through how to install and configure this library in both a plain React Native project (the “bare” workflow) and under the Expo Managed Workflow. We’ll also cover how to fetch the public key hashes from SSL Labs and how to test your pinning setup with Charles Proxy.


1. Installing

1.1 Plain React Native or Bare Workflow

  1. Install the library
    # npm
    npm install react-native-ssl-public-key-pinning
    
    # or yarn
    yarn add react-native-ssl-public-key-pinning
    
  2. Install pods on iOS
    cd ios && pod install && cd ..
    
  3. Rebuild your app
    For iOS, open the .xcworkspace in Xcode and build. For Android, use npx react-native run-android or open it in Android Studio.

1.2 Expo Managed Workflow (Step-by-Step)

Because Expo Go does not allow custom native modules, you can’t test SSL pinning using the default Expo Go client. Instead, you’ll need to create a custom development build or use a production build. Here’s the process:

  1. Install the library
    # npm
    npm install react-native-ssl-public-key-pinning
    
    # or yarn
    yarn add react-native-ssl-public-key-pinning
    
  2. Create a Development or Production Build
    • Development Build (Recommended for Testing)
      npx expo run:ios
      npx expo run:android
      
      This bundles the native module into your app so you can debug on a simulator or device.
    • Alternatively, create a Production Build
      npx expo build:ios
      npx expo build:android
      
      (Or use the newer EAS Build system.)
  3. (Optional) Disable Network Inspector on iOS Dev Client
    If you’re testing pinning on an iOS development build and notice that requests are not going through or are being intercepted, you might need to disable expo-dev-client’s network inspector (which can interfere with pinning).
    1. Install the Build Properties plugin:
      npx expo install expo-build-properties
      
    2. In your app.json or app.config.js, add:
      {
        "expo": {
          "plugins": [
            [
              "expo-build-properties",
              {
                "ios": {
                  "networkInspector": false
                }
              }
            ]
          ]
        }
      }
      
    3. Run:
      npx expo prebuild
      
    4. After that, rebuild your app (e.g., npx expo run:ios) so these settings take effect.
  4. Run Your App
    Once your custom build is installed on the simulator or device, you can proceed with the pinning steps below.

2. Retrieving the Pins from SSL Labs

A crucial part of SSL pinning is embedding the server’s public key hashes in your code. One easy way to retrieve these hashes is via SSL Labs:

  1. Navigate to SSL Labs
    Visit https://www.ssllabs.com/ssltest/ and enter your domain.
  2. Wait for the Scan
    After the scan completes, you’ll see details about your server’s certificates. For getting the 3 most important pins for your app, go to the Certification Paths and copy the pins to store in your project later, get all 3 of them or at least 2
  3. Grab the Public Key Info
    Look for a field that displays the SHA-256 public key hashes. These strings typically look something like mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=. You’ll use these in your code.
  4. Use Multiple Pins
    Best practice is to include at least two pins per domain (one primary and one backup). On iOS, the native library (TrustKit) enforces a minimum of two pins for each domain you configure.

3. Setting Up the Code

Now that you have your base64-encoded SHA-256 public key hashes, it’s time to embed them in your app:

  1. Import and Initialize Pinning
    Add the following to your main application file (e.g., App.js or a top-level component):
    import { initializeSslPinning } from "react-native-ssl-public-key-pinning";
    
    async function setupPinning() {
      await initializeSslPinning({
        "example.com": {
          includeSubdomains: true, // Whether to pin subdomains as well
          publicKeyHashes: [
            "mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=",
            "CLOmM1/OXvSPjw5UOYbAf9GKOxImEp9hhku9W90fHMk=", // Backup pin
          ],
          // Optionally set an expiration date (yyyy-MM-dd) for your pins
          // expirationDate: "2025-12-31",
        },
        // ...add more domains if needed
      });
    }
    
    // Call setupPinning() early in your app’s lifecycle
    setupPinning();
    
  2. Use Fetch or Axios as Usual
    Since this library hooks into React Native’s Networking layer, any standard network requests to example.com will now be validated against these pins.
  3. Listen for Errors (Optional)
    import { addSslPinningErrorListener } from "react-native-ssl-public-key-pinning";
    
    const subscription = addSslPinningErrorListener((error) => {
      // Error details about domain or pin mismatch
      console.warn("Pinning error on domain:", error.serverHostname);
    });
    
    // Clean up when unmounting or reloading
    // subscription.remove();
    

4. Testing with Charles Proxy

Finally, confirm your SSL pinning by attempting a MITM attack using Charles Proxy:

  1. Install and Configure Charles
  2. Attempt to Intercept Traffic
    • With Charles running, try making requests from your app (which has pinning enabled) to the pinned domain.
    • If your pinning is correct, Charles will not be able to decrypt or intercept those requests. Typically, you’ll see an error in Charles or the request will fail in your app if Charles tries to present a fake certificate.
  3. Validate Success
    • If you see requests failing when you attempt to intercept them, that means your pinning is working correctly.
    • If you do see decrypted responses in Charles or experience no error, double-check that your domain name matches your pinned configuration, and confirm you have the correct SHA-256 hash(es) from SSL Labs.
Profile picture
Luiz Fernando - Senior Software Engineer

Thanks for reading!

I hope you enjoyed reading this article. If you have any questions or feedback, don't hesitate to reach out to me on my social media bellow. Have a great day!

Carousel imageCarousel imageCarousel imageCarousel imageCarousel image

Latest Insights

Luiz Fernando

Senior Software Engineer

Senior Software Engineer with a passion for creating elegant solutions to complex problems

Connect With Me

Contact Me

If you're interested in learning more about my work, have a question, or just want to say hi, please don't hesitate to reach out.

© 2026 Luiz Fernando. All rights reserved