23

I'm building a small ReactNative iOS application. In it, I use the fetch API to make a simple get request to a server I control that has a valid - but self-signed - SSL cert. Understandably, this causes an error.

Between iOS/JSC, I'm unsure how (or what!) I can configure for my app to ignore this error - all the answers I've found so far are related to Objective-C solutions, I'm looking for something I can use with a JSC environment.

TehShrike
  • 9,855
  • 2
  • 33
  • 28
sgrove
  • 1,149
  • 2
  • 11
  • 23

3 Answers3

14

I encountered this same issue. As you noted, it seems the only way to get a native iOS app to work with a self-signed certificate is by writing/modifying Objective-C code, which is not a good approach for a JavaScript developer using React Native. For this reason, I think your question is an X/Y problem, and I propose solving your overall problem with a different approach from using a self-signed certificate in the first place.

I use a real certificate for development instead. Here is what I did to get my local development API working with SSL and React Native. It's free and simple.

  • ssh into your public server that your domain is associated with
  • install letsencrypt
  • generate a certificate for your development subdomains
    • dev.my-domain.com for developing my website/webapp locally
    • api.dev.my-domain.com for the api
    • ./letsencrypt-auto certonly --standalone -d dev.my-domain.com -d api.dev.my-domain.com
  • copy fullchain.pem and privkey.pem to your local machine
    • probably found under /etc/letsencrypt/live/dev.my-domain.com
    • one way to get the files from your remote machine: scp -r your_user@123.123.(...):/etc/letsencrypt/live/dev.my-domain.com ./
  • replace your self-signed certificate with fullchain.pem and privkey.pem
  • point dev.your-domain.com and other subdomains you use to your development machine's ip
    • you can modify your hosts file on each machine you want to use the domain
    • if your router has dnsmasq, you can do something like address=/dev.my-domain.com/192.168.(...) for the whole network (I recommend this)

At this point, because you are using a real, trusted certificate for the domain you're accessing, your api will now be trusted in browsers and on devices in development!

m59
  • 43,214
  • 14
  • 119
  • 136
  • 4
    Note, letsencrypt does not work for internal servers that are not publicly accessible (such as one running inside a vagrant environment). – IanVS Apr 25 '16 at 15:34
  • @IanVS Yes, but this shouldn't be a problem as long as you have a public server for your domain. My guide instructs to use letsencrypt on a public server that the domain points to and then copy the certificates to the development machine. – m59 Jan 03 '17 at 22:39
  • How does this work differently in production? The mobile device/client doesn't need anything special, right? – ryanwebjackson May 21 '20 at 19:28
  • @ryanwebjackson This solution is assuming you're using a different domain/ip/url of some sort during development, rather than pointing to your live production server, since in that case, no ssl cert would be needed for development anyway. – m59 May 22 '20 at 21:29
  • I like this solution, but, would I have to do this every 90 days? Do I have to manually distribute this to all the developers on the team. If the project is public, can I just have the dev certificate key+pem in the git repository and have some code that auto updates it in the repo? – Max Mar 13 '21 at 01:18
  • 1
    @Max I like to automate it. I run a docker service on the production server that renews the certificate for the production server and the dev certificate as well, and then it git commits the dev certificate, so it's available for local usage with a simple `git pull`. – m59 Mar 13 '21 at 03:32
  • Ok amazing. And is that ok to do if the repo is public? – Max Mar 13 '21 at 03:35
  • I asked a question specific to this and no one has answered as of yet: https://stackoverflow.com/questions/66608981/can-i-make-a-dev-ssl-certificate-public-if-not-how-do-i-distribute-the-certific – Max Mar 13 '21 at 03:44
  • I wouldn't want my dev certificates to be public. It would allow someone to deceptively/maliciously pretend to be at dev.yourdomain. – m59 Mar 15 '21 at 02:29
10

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

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

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>

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>
</dict>
</plist>

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

aleclarson
  • 18,087
  • 14
  • 64
  • 91
  • In addition to being dangerously insecure, that will also fail in bizarre ways if your service requires any HTTP auth. For more secure solutions (that don't have to be temporary), read https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html in Apple's developer library. – dgatwood Jan 04 '17 at 00:26
  • I'll just copy-paste this since you might've missed it 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. – Santiago Jimenez Wilson Apr 24 '18 at 19:42
  • 3
    The problem with temporary solutions is that they tend to end up shipping, in my experience. That's why I make it a point never to even suggest doing that temporarily. Either way, at a bare minimum, your snippet should really be wrapped with #if DEBUG. :-) – dgatwood Apr 24 '18 at 21:18
  • That I agree with... we devs sometimes forget how forgetful we are. Come to think of it, what you point out is some solid advice – Santiago Jimenez Wilson Apr 24 '18 at 22:47
  • 1
    Here's a git patch for the first example in this answer, which trusts all certificates for `react-native` requests: https://gist.githubusercontent.com/aleclarson/2bd4347a54d08d2abcbb96ebbb573070/raw/956cf3e2d7cd1ce1d0cb93671db5bb10496feb39/ssl-dev.patch – aleclarson Jul 17 '20 at 22:57
-4

This answer work for Android.

  1. find OkHttpClientProvider.java on node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\modules\network.

  2. compile ReactAndroid use NDK