Third-party sign-in in React Native - the TestFlight approval struggles

Martin Angelov
Sep 8, 2021
Categories:React Native

What is this article all about?

We have to support a sign-in with a third-party OAuth provider in our React Native mobile app.

In our case, the provider is Clever. As many others, they don't have a native picker (e.g. Google Accounts) or any sort of a React Native SDK.

This means that you have to implement a deep linking between your mobile app, the provider and your back end (optional).

One of the first steps that you have to do is to open a web browser with the login / OAuth login page of the provider. The article is all about this!

DISCLAIMER:
We won't go deep into the implementation of the third-party sign-in itself! We're going to add a step-by-step guide on this topic in the near future.

I know - this problem looks like something fairly mainstream but it blocked us from releasing the app to TestFlight for around 20 days!

It turned out that picking the right component that opens a web page and satisfies the Apple's guidelines could be a huge pain in the ass!

How to open a web page in React Native?

If you search for something like "How to open a web browser in React Native", you'll end-up with around 8 different solutions that all look promising. This can be hell for someone with less React Native experience.

Let me walk you through the different approaches that we took until we ended up with the right one. There are smaller details in the implementation of each component that is suitable for our needs and I'll try to explain and show them to you.

TL;DR: If  you want to go directly to the implementation that was approved by the TestFlight Beta Review team click here.

Using React Native's Linking

As you can imagine, we were reading the Linking docs while we're implementing the deep linking that we needed.

We noticed that there is an openURL() method that the Linking component supports which opens a web browser. We're like "OK, our problem sounds like a general use-case for the deep linking - it makes sense to use this one!". And we were wrong ...

Here is the actual code that opens the web browser:

 const url = `${cleverAuthUrl}?${encodedParams}`;

 const canOpenUrl = await Linking.canOpenURL(url);

 if (canOpenUrl) {
   await Linking.openURL(url);
 }
Open an URL using Linking.openURL

Here is how it looks in an Android and an iOS device:

Android
iOS

We submitted the app in this state to TestFlight and we started waiting for an approval. After around a week, we got rejected.

What's wrong?

Here is the response from the TestFlight Beta Review team:

Guideline 4.0 - Design
We noticed that the user is taken to Safari to sign in or register for an account, which provides a poor user experience.

Next Steps

To resolve this issue, please revise your app to enable users to sign in or register for an account in the app.

We recommend implementing the Safari View Controller API to display web content within your app. The Safari View Controller allows the display of a URL and inspection of the certificate from an embedded browser in an app so that customers can verify the webpage URL and SSL certificate to confirm they are entering their sign in credentials into a legitimate page.

What's actually wrong?

This is the first line from the Linking.openURL docs:

Try to open the given url with any of the installed apps.

In other words, Linking is basically using another app on your device to open the provided URL. You may have noticed this in the above GIFs. That's why the review says: "... the user is taken to Safari ..." even though you haven't added code like this.

According to the Apple's guidelines, it's a bad idea to take the users from one app to another automatically (without any manual action by the user). It's even a worse UX if you do it when they try to sign-in.

Using React Native's WebView

We were like: "OK, now we know what the exact problem is - the users must stay in the app while signing-in. It sounds like a really common one and React Native should have a proxy component that provides a proper API. Right?!".

WebView is now separated from the React Native's core but it's the official component that is suggested by the docs.

It's a simple native View that renders web content. Therefore, if we use it the users will stay in the app and see the page that we want to show.

Perfect! Let's go for it!

This is what we were thinking back then. And we were wrong...

Here is the actual code that makes the app show the page that we want:

const LoginCleverWebViewScreen = () => {
  return <WebView incognito source={{ uri: getCleverUrl() }} />;
};
Open an URL using WebView

Here is how it looks in an Android and an iOS device:

Android
iOS

As you can see, the user is not redirected to another app. Even the app navigation is preserved.

We submitted the app in this state to TestFlight and we started waiting for an approval. After around a week we got rejected again.

What's wrong?

It turned out that even though the WebView is the official implementation by React Native if you want to render a web page in your app, it doesn't fit with the Apple's guidelines and standards.

As I stated before, the WebView is just a simple native view that renders web content. The key here is mentioned in the second part of the TestFlight's review:

.. We recommend implementing the Safari View Controller API to display web content within your app ...

Using Expo's WebBrowser

After a fair amount of research, we realized that we are not using the right component. The best and correct way to achieve what we want to is to use an "in-app browser". In simple words, it's a native implementation that renders a web browser inside your app (at least this is how I understand it).

This can be achieved by using:

There is an official component that is maintained by Expo - the WebBrowser. These are the first two sentences that can be found in their docs:

expo-web-browser provides access to the system's web browser and supports handling redirects. On iOS, it uses SFSafariViewController or SFAuthenticationSession, depending on the method you call, and on Android it uses ChromeCustomTabs.

We had to eject the app when we started it and this was the main reason why we didn't look at Expo's components at first. After we realized there is no problem in using Expo's components even though the app is ejected, we decided to use the WebBrowser . And we were right!!!

Here is the code that opens the web browser:

const onCleverLoginPress = () => {
  WebBrowser.openBrowserAsync(getCleverUrl());
};
Open an URL using WebBrowser

Here is how it looks in an Android and an iOS device:

Android
iOS

We submitted the app in this state to TestFlight and we started waiting for an approval.

After around a week we were finally APPROVED!

As I said earlier, stay tuned to our blog - we'll write another blog post that covers the entire deep linking process step-by-step.

Conclusion

We didn't expect that something fairly straightforward would have such influence on the app review process.

I was really looking for an article like this that would have saved me all that back-n-forth with the app store.

I couldn't find one at the time, so I decided to write it.

Hopefully, this blog post is the exact thing that you were searching for!