Mutual TLS authentication for HTTP requests in React Native.
The client certificate and associated password are stored securely in the native Keychain.1
Once the module is set up, it applies to all normal react-native HTTP requests (e.g. through fetch
, XMLHttpRequest
, or any library that uses these) for HTTPS connections that ask for a client certificate. There is no overhead for connections that do not request a client certificate.
Only iOS is supported at this time, but pull requests are welcome if anyone wants to help add support for Android.
Install it as a dependency for your react-native project. You'll probably also need the native module for Keychain
unless you have some other way of getting the secrets into the keychain:
yarn add react-native-mutual-tls
yarn add react-native-keychain
npx pod-install
In order to use this module, you'll need a client certificate encoded as a p12
file, encrypted with a password.
The example project in this repository uses this test certificate from badssl.com
, but you'll need to provide your own.
The certificate and password are expected to be loaded into the native Keychain at runtime, because it's considered bad practice to hard-code them or embed them as static resources in your app bundle. You'll need to expect the user to supply these, or download them at runtime from some secure source.
Import the MutualTLS
module, as well as the Keychain
module.
import MutualTLS from 'react-native-mutual-tls';
import Keychain from 'react-native-keychain';
Optionally, set up debug information and errors from this module to go to the console for troubleshooting purposes. You could also provide different functions here if you wanted to do something else with the events.
If you don't do this, there will be no logging of such events.
MutualTLS.onDebug(console.debug);
MutualTLS.onError(console.error);
Before making a request, you'll need to load the secrets into the Keychain
.
Refer to the documentation for the Keychain
module for more information about managing secrets in the keychain, how to clear the secrets, and how to check whether the secrets are already loaded to avoid doing work to load them every time the app starts.
const myP12DataBase64 = "YOUR P12 FILE ENCODED AS BASE64 GOES HERE";
const myPassword = "THE PASSWORD TO DECRYPT THE P12 FILE GOES HERE";
await Promise.all([
Keychain.setGenericPassword('', myP12DataBase64, { service: "my-tls.client.p12" }),
Keychain.setGenericPassword('', myPassword, { service: "my-tls.client.p12.password" }),
]);
Next you need to call MutualTLS.configure
to tell the module where to find the secrets in the keychain.
MutualTLS
will not pre-load the secrets when configured - they will be loaded on the fly each time they are needed by an authentication challenge, so there is no need to call MutualTLS.configure
more than once even if the secret values change.
If you do not call MutualTLS.configure
, then the following defaults are used:
-
keychainServiceForP12
:mutual-tls.client.p12
-
keychainServiceForPassword
:mutual-tls.client.p12.password
If you're using MutualTLS
in a test environment with a proxied connection where the server name does not match the server name in the server certificate, you can also set the insecureDisableVerifyServerInRootDomain
option to the root domain for which you want to insecurely trust all subdomains. For example, setting it to example.com
would let you insecurely trust servers at a domain like bad.example.com
. DO NOT USE THIS SETTING IN A PRODUCTION ENVIRONMENT, as it defeats server authentication security features which form the other half of the "mutual" part of Mutual TLS.
// Use the same service names that were used in `Keychain.setGenericPassword`
await MutualTLS.configure({
keychainServiceForP12: 'my-tls.client.p12',
keychainServiceForPassword: 'my-tls.client.p12.password',
});
Assuming you've done all that setup, then you're ready to make secure Mutual TLS requests with a server that is configured to trust the client certificate you provided.
As stated before, any normal react-native HTTP request (e.g. through fetch
, XMLHttpRequest
, or any library that uses these) for HTTPS connections that ask for a client certificate will work, with no special options needed at request time.
const response = await fetch('https://my-secure.example.com/');
To see and run a fully working demonstration using https://client.badssl.com/
as the test server, see the example project in this repository.