React-native fetch() from https server with self-signed certificate

React Native

React Native Problem Overview


I'm trying to communicate with https server having self-signed certificate.

I can do this from .NET application (using ServicePointManager.ServerCertificateValidationCallback event), from native iOs application (using allowsAnyHTTPSCertificateForHost) or from web browser (just need to declare that the certificate is trusted).

But I can't get it to work in react-native application (neither in Android nor in iOS simulator).

I have tried different things but still not succeed.

I know there are some similar topics there:
<https://stackoverflow.com/questions/32892161/ignore-errors-for-self-signed-ssl-certs-using-the-fetch-api-in-a-reactnative-app><br> <https://stackoverflow.com/questions/31249559/react-native-xmlhttprequest-request-fails-if-ssl-https-certificate-is-not-vali><br> <https://stackoverflow.com/questions/33655832/fetch-in-react-native-wont-work-with-ssl-on-android><br> <https://stackoverflow.com/questions/34256675/problems-fetching-data-from-a-ssl-based-server><br> <https://stackoverflow.com/questions/30450414/unable-to-make-api-calls-using-react-native>

But they either do not contain answers or do not working (and they do not cover android programming at all). Searching other resources was not productive as well.

I believe there should be an easy way to work with self-signed certificate. Am I wrong? Does anybody know it (both for iOS and Android)?

React Native Solutions


Solution 1 - React Native

Disclaimer: This solution should be temporary and documented so that it won't stay in the production phase of the software, this is for development only.

For iOS, all you have to do is, open your xcodeproject (inside your iOS folder in RN) once you have that open, go to RCTNetwork.xcodeproj and in that project, navigate to RCTHTTPRequestHandler.m

In that file you will see a line like this:

#pragma mark - NSURLSession delegate

right after that line, add this function

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

And voila, you can now make insecure calls to your API without a valid certificate.

That should be enough, but if you are still having problems, you might need to go to your project's info.plist, left click on it and choose open as... source code.

and at the end just add

<key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
  <key>NSExceptionDomains</key>
	<dict>
		<key>localhost</key>
		<dict>
			<key>NSExceptionAllowsInsecureHTTPLoads</key>
			<true/>
		</dict>
		<key>subdomain.example.com</key>
		<dict>
			<key>NSIncludesSubdomains</key>
			<true/>
			<key>NSExceptionAllowsInsecureHTTPLoads</key>
			<true/>
		</dict>
	</dict>

so your file will look like this

    ...
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
	<key>NSLocationWhenInUseUsageDescription</key>
	<string></string>
  <key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
  <key>NSExceptionDomains</key>
	<dict>
		<key>localhost</key>
		<dict>
			<key>NSExceptionAllowsInsecureHTTPLoads</key>
			<true/>
		</dict>
		<key>subdomain.example.com</key>
		<dict>
			<key>NSIncludesSubdomains</key>
			<true/>
			<key>NSExceptionAllowsInsecureHTTPLoads</key>
			<true/>
		</dict>
	</dict>
</dict>
</plist>

For a real production ready solution, https://stackoverflow.com/a/36368360/5943130 that solution is better

Solution 2 - React Native

I am also faced the same issue in android. Finally i found the solution for this issue.

import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.ReactCookieJarContainer;

import java.security.cert.CertificateException;
import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.TimeUnit;


import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;

import static android.content.ContentValues.TAG;

public class CustomClientFactory implements OkHttpClientFactory {
    private static final String TAG = "OkHttpClientFactory";
    @Override
    public OkHttpClient createNewNetworkModuleClient() {
        try {
            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();



            OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    .connectTimeout(0, TimeUnit.MILLISECONDS).readTimeout(0, TimeUnit.MILLISECONDS)
                    .writeTimeout(0, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer());
            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            OkHttpClient okHttpClient = builder.build();
            return okHttpClient;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            throw new RuntimeException(e);
        }
    }

}

and inside our Android application MainApplication.java

 @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);  
    OkHttpClientProvider.setOkHttpClientFactory(new CustomClientFactory()); //add this line.
  }  

Its work for me. May be it will help to all.

Solution 3 - React Native

I got this working on Android by doing the following:

  1. Install the CA on your device under Settings -> Security & location > Advanced > Encryption & credentials > Install from storage. You can confirm it's installed correctly by visiting the domain through a web browser on your device. If the certificate validates, then the CA is installed.
  2. Create a network security configuration at res/xml/network_security_config.xml with the following contents. Update the domain(s).
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <!-- For React Native Hot-reloading system -->
        <!-- If you are running on a device insert your computer IP -->
        <domain includeSubdomains="true">localhost</domain>
        <domain includeSubdomains="true">your self signed domain</domain>

        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </domain-config>

    <base-config cleartextTrafficPermitted="false" />
</network-security-config>
  1. Reference this configuration file from your AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
                    ... >
        ...
    </application>
</manifest>

Solution 4 - React Native

This is happened due to self signed certificate used for encryption.Due to security reasons in android it demands C A authority signed or trusted certificates

use this plugin to avoid this.

https://www.npmjs.com/package/react-native-fetch-blob

RNFetchBlob.config({
  trusty : true
})
.then('GET', 'https://xxxxx.com')
.then((resp) => {
  // ...
})

Add config trusty as true to trust the certificate when you POST or GET API's

Solution 5 - React Native

Piggybacking of @Santiago Jimenez Wilson's answer here.

Obviously patching react-native itself is pretty dirty so we took the suggested override and extracted it into a category.

Just create a new file called RCTHTTPRequestHandler+yourPatchName.m somewhere in your project:

//
//  RCTHTTPRequestHandler+yourPatchName
//

#import "RCTBridgeModule.h"
#import "RCTHTTPRequestHandler.h"

@implementation RCTHTTPRequestHandler(yourPatchName)

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
@end

Next step would be to then differ between dev and prod and only overload the method for dev.

Solution 6 - React Native

Just to add information for users looking for Android Solution. As react-native do not handle SSL Error by default. There is a simple approach to Run your WebView for the websites that must be connected through "https" instead of "http".

I am assuming you have already installed the react-native-webview module using NPM if no then please google.

Once you have "react-native-webview" module inside "node_modules" folder. Go inside ".\node_modules >> react-native-webview >> android >> src >> main >> java >> com >> reactnativecommunity >> webview"

Open "RNCWebViewManager.java" File in Text Editor and Add Below Code

In import section add these two dependencies

....
import android.webkit.SslErrorHandler;
import android.net.http.SslError;
....

Now Search for Below "class" inside same file protected static class RNCWebViewClient extends WebViewClient

And add this method

@Override
public void onReceivedSslError(WebView webView, SslErrorHandler handler, SslError 
error) {
    if (error.toString() == "piglet")
        handler.cancel();
    else
        handler.proceed(); // Ignore SSL certificate errors
}

Next Save the file and Build your Project. It would not show Blank page now and handle the Invalid SSL Error.

Note:

  1. Solution works only if you are using "WebView" from "react-native-webview" instead of deprecated "WebView" from "react-native"
  2. Make sure you have already linked your "react-native" with "react-native-webview" else it would not be included inside your android project
  3. There might be version Error in "RNCWebViewModule", "RNCWebViewManager", "RNCWebViewFileProvider" classes (will be visible once you properly build your project using react-native run-android inside AndroidStudio after opening your android project using import) that you can easily fix using AndroidStudio

Solution 7 - React Native

Second answer is for android, and it is working fine. so use that for android. for ios actual answer is correct. but its hard to find the RCTNetwork.xcodeproj and also changes will gone if you delete and add the npm modules. so it shard to maintain.

so i have created a javascript for patching. just executing the below script with node js will patch. passing the argument -r will remove the patch.

 * run this script normally for patching,
 * pass -r argument at the end of the command for removing the patch
 * ex: 
 * patching:         $ node patch_ssl_bypass.js
 * removing patch:   $ node patch_ssl_bypass.js -r
 */

var fs = require('fs');

const isRemove = process.argv[process.argv.length-1] == '-r';

const file =
  'node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm';
const delemeter = '#pragma mark - NSURLSession delegate';
const code = `
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
`;
console.log('#############   Reading File   ###############');
fs.readFile(file, 'utf8', function(error, data) {
  if (error) {
    console.log('#############  error reading file  ###############');
    console.error(error);
    return;
  }
  if (data.indexOf(code) < 0 && !isRemove) {
    console.log('#############  Patch is not done.  ###############');
    console.log('#############  Patching file  ###############');
    var parts = data.split(delemeter);
    var newCodeBlock = parts[0] + delemeter + '\n' + code + '\n'+parts[1];
    fs.writeFile(file,newCodeBlock,function(){
        console.log('#############  Successfully patched file  ###############');
        console.log('#############  re build the ios project  ###############');
    })
  }else{
      if (isRemove){
          var updatedCode = data.replace(code,'');
        fs.writeFile(file,updatedCode,function(){
            console.log('#############  Successfully removed patch  ###############');
            console.log('#############  re build the ios project  ###############');
        })
      }else{
        console.log('#############  File already patched. No need again  ###############');
      }
  }
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBoris WMView Question on Stackoverflow
Solution 1 - React NativeSantiago Jimenez WilsonView Answer on Stackoverflow
Solution 2 - React NativeuserView Answer on Stackoverflow
Solution 3 - React NativeAli GangjiView Answer on Stackoverflow
Solution 4 - React NativeArjun RamdasView Answer on Stackoverflow
Solution 5 - React NativePhilip PaetzView Answer on Stackoverflow
Solution 6 - React NativesagarView Answer on Stackoverflow
Solution 7 - React Nativeramachandra s joshiView Answer on Stackoverflow