Blog
Deep Links, Universal and Asset Links

Deep Links, Universal and Asset Links

Do you want to dive deep into the topic of Deep Links, Universal Links and App Links? Wait no more and take a read!
Tags
Linking
React Native
Universal Links
Deep Links
Asset links
App Links
Published on
February 23, 2024

Introduction

While developing different kinds of software, we often find ourselves in a need to navigate in between the platforms or services. As programmers, we have quite a few ways to do so, be it a simple URL link or a deep link. And while redirecting with an ordinary link seems quite easy, handling this redirection with aforementioned deep links, universal links or asset links might be a bit harder while having a mobile app in our domain. Especially for somebody who is new to the topic.

In this article I will walk you through the aforementioned deep links, universal links and app links: first starting with some theoretical knowledge in a nutshell, and then diving into a little bit of coding!

Deep Links

What is a Deep Link?

Deep links are a special kind of links that can automatically open up the mobile application and send the user straight to a certain screen within the app, significantly improving the UX and the time which the users would spend on navigating to a certain location themselves. This form of communication can also act as some kind of information carrier thanks to the URL params, enabling developers to pass data like tokens, pre-filled forms, and others.

When clicking on a deep link, the user will be asked whether he would like to open the app (on iOS) or he will see a dialog asking which app should be used to open it (on Android) – you can open a link to Google Maps in, e.g., Chrome or Google maps app.

As for the structure of deep links - they look different from your usual link: instead of the commonly known https:// URL scheme, we have this custom one defined by the developer. The rest of the link, like host, path and optional params, are just like a normal link.

Example deep link:

Deep Link scheme

How deep links can be used

As mentioned before, deep links can significantly increase the UX of our applications. We can implement them in various ways, serving different kind of tasks, be it for example:

  • account activation
  • user authentication
  • navigating to a certain screen with a link
  • invite/discount links

The possibilities are endless. Those mentioned are just a few.

To trigger the app open, we can treat a deep link just like a normal url - by simply passing it as href parameter when on web or using Linking module from React Native if we want to do it from within the mobile application (it works this way too 😎). We also have the option to programmatically update the window.location.href in the web browser, but we need to be aware that this is not the perfect solution as we simply do not know whether the app is present on the device. On top of that, we need to check the device type - we obviously cannot install any mobile app on desktops.

Let’s get further down the road and get through the setup of deep links for each platform.

Deep Links setup for iOS

Having gone through the theory, let’s see how we actually set up and use deep links on iOS, using the starter app I mentioned in the introduction.

First and foremost, we need to (1.) update the implementation file AppDelegate.m and add a few new methods there. Second, we need to tell the app which URL scheme it should listen for (2.). Both of these steps can easily be done using XCode.

Note that you can complement the knowledge presented in this article with this really awesome and thorough documentation: React Navigation Documentation -> [Deep Linking Setup].

Let’s tackle the AppDelegate.m file then (1.):

ios/OAuthReactNative/AppDelegate.m:

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
  return [RCTLinkingManager application:application openURL:url
                      sourceApplication:sourceApplication annotation:annotation];
}

- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
 restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
 return [RCTLinkingManager application:application
                  continueUserActivity:userActivity
                    restorationHandler:restorationHandler];
}

@end

Don’t forget about importing RCTLinkingManager ☝🏽 :

ios/OAuthReactNative/AppDelegate.m:

#import "AppDelegate.h"

#import <React/RCTLinkingManager.h> // <- added
#import <React/RCTBridge.h>

What we have done here was adding three methods: openURL, yet another openURL and last but not least, continueUserActivity. The latter is not particularly needed for deep links to work, but it will be required when moving further along, for Universal Links.

Now in order to set up the deep links correctly, we need to add the URL scheme (2.). For the purpose of this article, let’s go with oauthrn:// for example 🦾.

Deep linking schema config - iOS

That’s pretty much it as far as iOS is concerned. Now you can easily test if you’ve implemented everything correctly by firing up the app in the simulator and executing this command from your shell:

npx uri-scheme open oauthrn://test --ios
Testing Deep Link setup on iOS

Next, yet another platform to be configured! This time, it’s Android. Shall we?

Deep Links setup for Android

Same as with the iOS setup, complement your knowledge with React Navigation Documentation. In order to set up everything for Android, we have to go through two steps (all within the AndroidManifest.xml file):

  • have the launchMode of the MainActivity set to singleTask
  • add intent-filters with VIEW type action inside the MainActivity for a given deep link scheme (in our case oauthrn)

This is the configuration from our app:

android/app/src/main/AndroidManifest.xml

<activity
    android:name=".MainActivity"
    android:label="@string/app_name"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
    android:launchMode="singleTask"
    android:windowSoftInputMode="adjustResize">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="oauthrn" />
    </intent-filter>
</activity>

We are pretty much done, there is no need to do anything more. Similarly to the iOS, we can test our setup with this command:

npx uri-scheme open oauthrn://test --android
Testing Deep Link setup on iOS

Given that we will be coding along a simple app that handles both kinds of links later, here is the starter repo with the initial setup done: React Native OAuth Linking App - deep links config

Universal and App Links

What is a Universal/App Link + differences ?

Universal and App Links are different from Deep Links. The former (Universal Links) are iOS-specific links and the latter (App Links) are Android-specific. In general, at first glance their behavior is pretty much similar to Deep Links - such links can open a mobile app and carry a certain kind of payload, significantly improving the UX and enriching multiple use cases.

The content assigned to a given URL is present for both mobile and web (meaning that e.g. a product page can be opened in the web browser, as an oridinary web app, or the link can open the mobile app which would automatically redirect you to a certain screen in the app with the same data about the same product - Amazon’s app and web page would be a great example), thus in case that anything goes wrong on the mobile side/redirection, the proper content is still being served to the user on the web. But this is not the case with Deep Links, as they have no content assigned to the web. We simply won’t access anything on web with this other than an error page (given that we don’t have the app installed).

As one would easily guess, there is one more major difference.

The main thing that differentiaties them from last chapter’s Deep Links is how they look - an Universal/App Link is simply just a regular URL like any other with the difference being that it is bound to the mobile application. By bounding, I mean that a certain application (judged by its bundle id/package name) has to be assigned to the domain. This is done by hosting a properly formatted file on your web server. On top of that, this must be followed by some further setup in the application codebase - adding Associated Domain entitlement on iOS and adding a new intent-filter on Android (similarly to Deep Links). This particular characteristic makes the Universal/App links a bit safer as two-way authentication is required - config on the application side and then on the web server side. During the app installation, the system downloads the config files from the web server and checks them against the app setup.

Universal Link / App Link scheme

How Universal/App Links can be used

The ideal use cases for Universal/App Links would be the same as the ones proposed for Deep Links in one of the previous chapters. Although the use cases are no different, there is one thing that will make Universal/App Links stand out - they fallback to a web page.

How do we handle account confirmation? We probably already have it present in our web app at the moment of developing the mobile app. What we can do is simply assign the exact same link for account confirmation that we already have on the web to a linking configuration on mobile and upon clicking on the link in the email, open the mobile app and make proper calls to our BE. In case a user opens the link on desktop, they are simply presented with the web version. This makes it a better UX for a user and a better developer experience for us, developers.

Universal/App Links setup (hosting files)

Based on what we already know from the previous chapter, there is a need to host a proper file on our domain in order for Universal Links and App Links to work*. Let’s go through it in this section!

* You can actually ignore it for Android. As a result, before opening a link the user will be prompted with a dialog that will ask them how they want to handle the link - using a browser or a mobile app (like with a deep link). When we host the assetlinks.json file, no dialog will be opened.

The files that must be hosted on web server are:

  • apple-app-site-association file for iOS
  • assetlinks.json for Android

Both of these files have to be hosted on your domain, at /.well-known/{FILE} path (for iOS, it can be either /.well-known or /). If you were to have the my-app.com domain, then these files should be accessible at:

  • https://my-app.com/.well-known/assetlinks.json
  • https://my-app.com/.well-known/apple-app-site-association

Both should be content-type: application/json. Note that apple-app-site-association (AASA) does not have any extension. Host it as just apple-app-site-association (NOT! apple-app-site-association.json 👨🏽‍🏫).

Side note: none of these files have to be encrypted.

Now that we know where to host them, we should also take a look at their content. Let’s start with iOS and its AASA file.

AASA (apple-app-site-association file)

Contrary to Android (assetlinks.json, more on it later), Apple requires us to put all the paths config in the AASA file itself, whereas on Android (assetlinks.json) all this config is done in the app code.

Let’s start and create the AASA for our application.

The file is basically a JSON, so most developers should not be surprised. Note that a correctly formatted file is a must for it to work (e.g. use double quotes, not single ones etc.).

Here is an example:

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appIDs": ["TEAM_ID.com.twg.oauthreactnative"],
        "appID": "TEAM_ID.com.twg.oauthreactnative",
        "paths": ["/path", "/path/*", "/path?", "*"],
        "components": [
          {
            "/": "/path",
            "?": {"token": "*"},
            "comment": "Matches any URL whose path starts with /path and which has a query item with name 'token' and any value"
          },
          {
            "/": "/*",
            "comment": "Any"
          }
        ]
      }
    ]
  },
  "webcredentials": {
    "apps": ["TEAM_ID.com.twg.oauthreactnative"]
  }
}

Like in the example above, the AASA file is an object. The config for the universal links must be located under the applinks key. The aforementioned JSON object can also contain a different configuration, e.g., webcredentials if we were to implement credentials saving from our forms to the iOS keychain. But this won’t be our focus as it is beyond the scope of this article.

The value of applinks is another object in which we have to include the apps property, which is an empty array. We can exclude it only if we do not want to support iOS versions 12 and below.

Next, we have the details property and one can say that this is the core config. Inside, appID or appIDs fields have to include the app id (either just a string for appID or an array of strings for appIDs). App ID consists of a team ID and the application’s Bundle Identifier split with a dot. The former can be found for instance on Apple Developer Page, in the upper right corner (see: screenshot from below, it has this 10 character long string form - ABCD1234AB). The latter is simply the app id - you can check it in XCode, under the General -> Bundle Identifier tab.

Apple Team ID example location

The paths and components fields represent the paths config - which path should be “attached” to the mobile app. And although both of these properties result in the same thing, the components property is much more configurable and is a newer addition to the AASA. That said, you can add both though if you want to support older iOS versions. If you’ll be using both components and paths, the parser will ignore the paths setup in favor of components when installing/updating the app (on iOS versions that supports components property).

So in order for the pattern to match a set of paths on your domain, you can do the following for paths array:

{
  "paths": ["/*"], // <- will match every path
  "paths": ["/some_path/*"], // <- will match every path at /some_path
  "paths": ["/??/dashboard"], // <- will match every path that has two characters in place of ?, e.g. /pl/dashboard or /en/dashboard
  "paths": ["/coupons#AB??", "NOT /coupons#ABOO"], // will match every path at /coupons with # and 4 character long code, starting with A and B while also explicitly exluding coupon ABOO
}

Pattern matching for components setup is much more sophisticated and can look more or less like this:

{
  "components": [
    // Match for every path at /path ->  /path/something OR /path/another
    {
      "/": "/path/*",
    },
    // Match for every path at /path/ having at least one character after the second / ->  /path/something OR /path/another but NOT /path/
    {
      "/": "/path/?*",
    },
    // Match for /menu path with query params lang and regions (both of them having at least one character)
    {
      "/": "/menu/*",
      "?": {"lang": "?*", "region": "?*"}
    },
    // Exluding match for path having 4 characters at first URL block and then /order with hash being payment_method=XY explicitly
    {
      "/": "/????/order",
      "#": "payment_method=XY",
      "exclude": true
    },
    // Match for path having 4 characters at first URL block and then /order with hash payment_method evaluating to a two characters long value, starting with X
    {
      "/": "/????/order",
      "#": "payment_method=X?"
    },
    // Blocks may have also the comment property 😀
    {
      "/": "*",
      "comment": "Any comment you wish to include"
    }
  ]
}

As for the wildcards used above, * will match every string, whereas ? will match any one character.

To learn more, see the links below. You can find far more information about configuring the Universal Links with AASA file (like substitution variables):

How does associating the mobile app with the AASA file works behind the scenes? Every time that the application is being installed or updated, the iOS downloads this file from your server and validates it. Since iOS 14 and macOS BigSur, Apple has introduced the Apple CDN server which caches these files - every app install will check if this file is cached on the Apple’s CDN and if not, it will try to download it from your server. This way, it may significantly decrease the load on your server.

After we have sucessfully edited and hosted the AASA file, there is still one thing we have to change in the app files - we have to add the Associated Domains capability in the XCode. Open your .xcworkspace, select your project and simply click on + Capability , just like in the video below. After adding this capability (it is available only for accounts with an active developer programme), you have to add the domain name.

It is as simple as adding a new entry with a string that starts with applinks: and is appended with the domain name. When adding the domain, remember that you have to omit the www and https part. An example entry would be:

applinks:myawesomeapp.com

If you happen to have multiple subdomains, you can use a wildcard:

applinks:*.myawesomeapp.com
Adding Associated Domains capability
Associated Domains added

Android

We have a quite similar situation for the Android platform. As mentioned above, we have to upload assetlinks.json file on our server in order to connect the mobile app with our website.

Contrary to Apple, in case of assetlinks.json file, the path config is to be handled in the application code, not in the hosted file itself. The file should contain only the neccesary information like package name and sha256 fingerprints of your app’s signing certificate.

The example file looks like this:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.oauthreactnative",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

The key elements, as stated before, are package_name and sha256_cert_fingerprints.

package_name should be the same as the one from your AndroidManifest file. sha256 fingerprints is an array so you can include values, e.g., for your release and debug applications. To quickly generate each fingerprint, you can use

keytool -list -v -keystore my-key.keystore

to generate this. Don’t forget to exchange my-key with the correct name of your key :)

All the remaining properties, like relation or namespace, are constant for every configuration and required for the system to correctly associate the web side with the mobile application.

What about when we want to associate multiple applications? The assetlinks.json contains an array of this configs, so it is just a matter of appending this array with another JSON object containing the appropriate package name and at least one set of sha256 fingerprints, like so:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.oauthreactnative",
      "sha256_cert_fingerprints":
      ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
    }
  },
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.oauthreactnative_2",
      "sha256_cert_fingerprints":
      [
        "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5", "15:6D:E9:83:C5:80:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:AD:A8:8A:04:96:B2:3F:CF:44:E5"
      ]
    }
  }
]

Now let’s go to the paths config. Paths config is similar to deep links with the difference being that the intent-filter should have the https scheme, not the custom one.

Intent-filter should also have the android:host, android:path and android:pathPrefix properties.

An example config can look like this:

<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="https"
      android:host="my_domain.app"
      android:pathPrefix="/my_path"/>
  </intent-filter>

<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="https"
      android:host="my_domain.app"
      android:pathPrefix="/my_second_path"/>
</intent-filter>

All intent-filters should be nested in the MainActivity similarily to deep links.

Notice the autoVerify flag. When android:autoVerify=“true” is present on any one of your intent filters, installing your app on devices with Android 6.0 and higher causes the system to attempt to verify all hosts associated with the URLs in any of your app’s intent filters

How does it work?

The system will check for intent-filters including:

  • Action: android.intent.action.VIEW
  • Categories: android.intent.category.BROWSABLE and android.intent.category.DEFAULT
  • Data scheme: http or https

and for each unique host name located in the declared intent filters, Android will then query the corresponding URLs for the assetlinks.json file at https://my_domain.app/.well-known/assetlinks.json.

As for the pattern matching the paths - find examples with some comments below:

<!-- Will match /my_path | /my_path/something | /my_path?param=value -->
  <data android:scheme="https"
    android:host="my_domain.app"
    android:pathPrefix="/my_path"
  />
<!-- Will match only /my_path -->
  <data android:scheme="https"
    android:host="my_domain.app"
    android:path="/my_path"
  />
<!-- Will match only /my_path | /mypath | /mysubpage | /my_dashboard etc -->
  <data android:scheme="https"
    android:host="my_domain.app"
    android:path="/my.*"
  />

When trying to visit https://my_domain.app/my_path or https://my_domain.app/my_path?param links, the app will open!

Universal/App Links setup (React Native code -> Linking configuration)

Differences between Deep Links, Universal Links and App Links

By now, you should have an idea of what are the main differences between deep links and universal (iOS) / app (Android) links.

To recap, here are the main conclusions in a nutshell:

  • URL scheme: deep links use a custom one, defined by the developer, whereas universal/app links - a HTTPS one.
  • Prone to glitches related to cases of an uninstalled app: deep links do not handle a lack of the app on the user’s device by default, whereas universal/app links do - they simply fallback to a website (given that we host anything at a specific path)
  • Security: universal/app links are much more secure - they have to be synched with an app by hosting a proper file at your company’s domain thus only a given bundle id / package name can be opened by the link. With deep links, we just define a scheme and the app opens

Perhaps you will also find the following table useful for comparison 🙃

Deep Links and Universal / App Links differences

Google OAuth application with Deep Links

Handling login / registration with Google

In this part we will start working on our aforementioned app and focus on implementing a prototype of two simple features - google login with deep links and account confirmation with universal / app links.

If you want to code along using my setup, feel free to start from the starter repo. You can also take a look at the config files that I hosted on my domain oauth.danielgrychtol.com and get some inspiration. The domain is also added in the Associated Domains capability. The same goes for the Android setup -> the starter repo is already configured for this domain.

My config files:

Also, for the purpose of the article, I’ve set up a simple backend app that is hosted on the same domain. You can find the source code on GitHub as well - OAuth Google Backend.

Adding Associated Domains capability
Associated Domains added

Adding React Navigation and handling URL paths

If you’ve followed through and set up everything, your locally run app should be opened upon clicking on a deep link / universal link. What we still lack though is the ability to redirect a user to a certain screen based on the URL that is opened. We can easily do it with react-navigation library. Our starter has the library already installed (link to the repo), along with the linking config passed to NavigationContainer. We just have to correctly populate all the data. More information on linking setup available in the docs to react navigation linking

We want to sync two URLs with our app: the first one being our deep links and the second being our domain that has the AASA and assetlinks.json files hosted.

Let’s get our hands and keyboard dirty and add the domains (deep link scheme and domain oauth.danielgrychtol.com) to the prefixes array in src/navigation/Linking.tsx:

src/navigation/Linking.tsx

import {LinkingOptions} from '@react-navigation/native';
import {baseDomain, deepLinkScheme} from '../constants/Domains';

export const linking: LinkingOptions = {
  prefixes: [deepLinkScheme, baseDomain],
  config: {
    screens: {}
  }
};

src/constants/Domains.ts (create one):

export const deepLinkScheme = 'oauthrn://';
export const baseDomain = 'https://oauth.danielgrychtol.com';

Next, we want to assign two paths to our routing: one for handling the callback from Google OAuth and the second one for handling account confirmation:

src/navigation/Linking.ts

export const linking: LinkingOptions = {
  prefixes: [deepLinkScheme, baseDomain],
  config: {
    screens: {
      [MainNavigationRoutes.REDIRECT]: {
        path: 'oauth_redirect/:token'
      },
      [MainNavigationRoutes.ACCOUNT_CONFIRM]: {
        path: 'account_confirm/:token'
      }
    }
  }
};

In a real life scenario, it may be likely be that you are going to have some nested navigators. Handling the redirection in such case is not that hard, it is just as simple as adding a new screens key to a given screen.

src/navigation/Linking.ts

export const linking: LinkingOptions = {
  prefixes: [deepLinkScheme, baseDomain],
  config: {
    screens: {
      ...
      [MainNavigationRoutes.ACCOUNT_CONFIRM]: {
        screens: {
          [OtherRoutes.SOME_ROUTE]: {
            path: 'other_route'
          },
        }
      }
    }
  }
};

But we don’t need it in our simple app.

Now, both of our routes need to handle the URL params. By declaring the path like we just did above, we can simply extract them by using the useRoute hook and get the e.g. the token value from the URL params. Take a look at the below example:

export enum MainNavigationRoutes {
  MAIN = 'MAIN',
  REDIRECT = 'REDIRECT',
  ACCOUNT_CONFIRM = 'ACCOUNT_CONFIRM'
}

export type MainNavigationParams = {
  [MainNavigationRoutes.MAIN]: {};
  [MainNavigationRoutes.REDIRECT]: {
    token: string;
  };
  [MainNavigationRoutes.ACCOUNT_CONFIRM]: {};
}

type Route = RouteProp<MainNavigationParams, MainNavigationRoutes.REDIRECT>

const { params: {token} } = useRoute<Route>() || {params: {token: ''}}

Next, let’s edit our Confirm Account and Redirect screens and handle extracting the token from params and then fire up some dummy logic which you can later substitute with calls to the back end:

types/MainNavigation.ts

export enum MainNavigationRoutes {
  MAIN = 'MAIN',
  REDIRECT = 'REDIRECT',
  ACCOUNT_CONFIRM = 'ACCOUNT_CONFIRM'
}
j
export type MainNavigationParams = {
  [MainNavigationRoutes.MAIN]: {};
  [MainNavigationRoutes.REDIRECT]: {
    token: string;
  };
  [MainNavigationRoutes.ACCOUNT_CONFIRM]: {
    token: string;
  };
};

src/screens/Redirect.tsx

type Route = RouteProp<MainNavigationParams, MainNavigationRoutes.REDIRECT>;

const Redirect = () => {
  const {
    params: {token}
  } = useRoute<Route>() || {params: {token: ''}};

  useEffect(() => {
    Alert.alert('Token:', token);
  }, [token]);

  return (
    <View style={styles.container}>
      <Text>Redirect screen</Text>
    </View>
  );
};

src/screens/AccountConfirm.tsx

type Route = RouteProp<
  MainNavigationParams,
  MainNavigationRoutes.ACCOUNT_CONFIRM
>;

const AccountConfirm = () => {
  const {
    params: {token}
  } = useRoute<Route>() || {token: ''};

  useEffect(() => {
    Alert.alert('Token:', token);
  }, [token]);

  return (
    <View style={styles.container}>
      <Text>Account confirm screen</Text>
    </View>
  );
};
AccountConfirm screen above has no styles or imports whatsoever, remember to add them yourself or use the base of the file from building-app or master branch of the demo app ☺️

At this point, when we build our app and try out the linking, the app should know which screens to open. We covered the topic of how to test-open links in one of the previous chapters*. You can also use the Notes app or send an email with a link to yourself. All methods should open our apps:

Deep Links screens setup iOS
Universal Links screen setup iOS
Deep Links screens setup Android
App Links screen setup Android
npx uri-scheme open oauthrn://test --ios

It should behave similarly with the second screen.

Now that we: 1) have our app synched with both deep links and universal/app links, 2) paths are assigned to a certain screens and 3) we are able to fetch the route params, we can now start implementing two features:

  1. OAuth login/signup that is web-based (we will display an in-app browser that will be closed by a deep link for which it is listening in the end)
  2. Account confirmation based on universal / app links -> clicking a link e.g. in an email will open the mobile app

As mentioned at the beginning of this chapter, we have the backend ready. If we were to visit https://oauth.danielgrychtol.com/auth/google, we would be prompted with the Google OAuth form.

If we successfully authorize with Google, we end up on the https://oauth.danielgrychtol.com/auth/oauth_redirect/:TOKEN path. Now, we want to fetch the token from the path and pass it to the mobile app so that we can use it to get user data on our app side.

To do that, we will use react-native-inapp-browser-reborn. It listens for a deep link and will allow us to get the link and do whatever we want with it. In our case we will get the token from the link and display it in an Alert. In your production app, you will most likely trigger a user fetch or something similar

Let’s first install the in-app browser package:

yarn add react-native-inappbrowser-reborn

Proceed with the rest of the setup by following these steps - React Native InApp Browser Reborn

Now that we have the in-app browser installed, we can try to implement the Google OAuth. Let’s add a button in our Main.tsx file:

src/screens/Main.tsx

const Main = () => {
  const onPress = () => {
    // ...
  };

  return (
    <View style={styles.container}>
      <Text>Main screen</Text>
      <View style={styles.buttonContainer}>
        <Pressable style={styles.button} onPress={onPress}>
          <Text style={styles.buttonText}>Log In with Google</Text>
        </Pressable>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  buttonContainer: {
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 8
  },
  button: {
    backgroundColor: '#79dff4',
    padding: 12,
    paddingHorizontal: 16,
    borderRadius: 2,
    elevation: 2,
    shadowOpacity: 0.1,
    shadowOffset: {
      height: 2,
      width: 0
    },
    shadowRadius: 0.2
  },
  buttonText: {
    color: '#fefefe',
    fontWeight: 'bold'
  }
});

Nice! Now let’s add a function which would display the in-app browser with the OAuth flow handled by our backend:

Please note that the InAppBrowser’s openAuth method assumes a parameter that is the path which is supposed to resolve the action triggered by the library. It won’t work if the exact same path is added to the AASA file (issue is iOS only)

src/services/oauth.ts

import {Linking, StatusBar} from 'react-native';
import InAppBrowser from 'react-native-inappbrowser-reborn';
import {deepLinkScheme, oauthWithGoogleRoute} from '../constants/Domains';

export const logInWithGoogle = async () => {
  try {
    InAppBrowser.closeAuth();

    if (await InAppBrowser.isAvailable()) {
      StatusBar.setBarStyle('light-content');

      InAppBrowser.openAuth(
        oauthWithGoogleRoute,
        deepLinkScheme + 'oauth_redirect',
        {
          // iOS Properties
          ephemeralWebSession: false,
          // Android Properties
          showTitle: true,
          enableUrlBarHiding: true,
          enableDefaultShare: false
        }
      ).then(async (response) => {
        StatusBar.setBarStyle('dark-content');

        if (response.type === 'cancel') {
          //   do sth on cancel
        }

        if (response.type === 'success' && response.url) {
          await Linking.openURL(response.url);
        }
      });
    } else {
      Linking.openURL(oauthWithGoogleRoute);
    }
  } catch (error) {
    StatusBar.setBarStyle('dark-content');
    Linking.openURL(oauthWithGoogleRoute);
  }
};
Remember to add oauthWithGoogleRoute const in your constants file at src/constants/Domains

and let’s invoke it in our Main.tsx screen after pressing the button:

src/screens/Main.tsx

const onPress = () => {
    logInWithGoogle();
};

Now, if we were to try to press the button, we would be presented with the Google web-based OAuth. When we finish, the route will fire up the deep link which will result in our app being brought back to the foreground. Thanks to the setup, we have access to the token from our BE, which we could use to fetch the user data from backend :)

Congrats! The registration prototype is working. You can freely keep going and add another functionality, i.e., account confirmation.

Handling account confirmation

Usually, the account confirmation feature works in a following way in most apps: a user registers and receives an e-mail with some kind of a confirmation link. When he clicks on it and is redirected to a page / app, the account gets confirmed by means of a call to the backend that takes place behind the scenes. We are going to mimic this in this article and code it in the form of a prototype.

The backend app that I’ve prepared is supposed to send an email containing a proper link every time we authenticate. Clicking the link should open our mobile app and since we already have a screen and redirection configured, we are basically done with the app at this moment. The current behavior of the app would then be, in our case, displaying an alert with the extracted token from the URL. Going further with the feature would be out of scope and by now you should already have an idea of how we can set up such features and code them.

Conclusion

I hope that you found this article helpful and interesting. In case of any questions, please do not hesitate to ask on our Discord server or drop us a message on GitHub. Happy coding!

Thank you! Your submission has been received! 📨
Oops! Something went wrong while submitting the form.
Linking
React Native
Universal Links
Deep Links
Asset links
App Links

Build and release your app with Fastlane!

Continuous Deployment
Fastlane
React Native
Mobile development
Ruby
In this article you will learn how to save your time. Try out this Fastlane setup for both Android and iOS!
See article

Our React Native toolbox: 24 essential tools for developers in 2024

React Native
Toolbox
Mobile development
Explore 24 tools that every React Native developer needs in 2024!
See article

React Native Video on VisionOS

React Native
React Native Video
visionOS
Mobile
Exploring the intersection of React Native Video and VisionOS, this article offers a detailed roadmap for developers looking to harness the power of React Native for video viewing and manipulation in the VisionOS environment.
See article
Do you need help with developing react solutions?

Leave your contact info and we’ll be in touch with you shortly

Leave contact info
Become one of our 10+ ambassadors and earn real $$$.