Using Auth0 for authenticating users in a React Native chat app

Using Auth0 for authenticating users in a React Native chat app
In this tutorial you will put an authentication system in place via Facebook/Google with Auth0 to log in users in a chat app. Specifically you will learn how to set up authentication in a chat app with Auth0, use Auth0 in a React Native app, and integrate Auth0 with Chatkit.

In this tutorial, we’ll be looking at how you can use Auth0 to log in users. By the end of this tutorial, you’ll learn how to:

  • Set up authentication with Auth0.
  • Use Auth0 in a React Native app.
  • Integrate Auth0 with Chatkit.

Most mobile apps require users to log in with their email and password, or any of the third-party login providers to use their service.

Popular chat apps such as Messenger and WhatsApp both have an authentication system in place to verify the identity of their users as well as store their information.

One of the limitations of Chatkit is that it doesn’t integrate with authentication services by default.

Prerequisites

Basic knowledge of React Native is required to follow this tutorial. You should know how to install native modules and run the app on your selected environment.

The following package versions are used in this tutorial:

  • Node 11.2.0
  • Yarn 1.13.0
  • React Native CLI 2.0.1
  • React Native 0.59.5

If you encounter any issues getting the app to work, try using the above versions instead.

App overview

We’re going to update a pre-coded chat app created using Chatkit and React Native Gifted Chat. It has the following features:

  • Login using an existing Chatkit user.
  • Listing the user’s rooms.
  • Sending and receiving messages.
  • Loading older messages.

We’ll update it so it uses Auth0 for authenticating the user. When a user logs in with Auth0 (either Facebook or Google), we submit their basic user data to the server and create a corresponding Chatkit user account (if it doesn’t already exist). After that, we authenticate the user to use Chatkit.

On the client side, we then securely store their access token and refresh token so we can automatically authenticate them the next time they log in.

You can view the code used in this tutorial on this GitHub repo.

Setting up the authentication

In this section, we’ll be looking at how you can set up Auth0 and create the corresponding auth clients for Google and Facebook. We’ll be using those two providers to authenticate the users.

Setting up Auth0

The first step is for you to create an Auth0 app instance:

Once the app is created, select React Native under the Quick Start tab:

That will show you how to configure your React Native project to use Auth0. Follow the instructions in that page after going through the Bootstrapping the app section. The following details are already mentioned in that documentation, but I’ll mention it again because it’s very important.

In your application settings, input a value to the Allowed Callback URLs field:

For me, it looks like this. The first one is the callback URL for Android, and the second one is for iOS:

    com.rnchatkitauth0://wern.auth0.com/android/com.rnchatkitauth0/callback,
    org.reactjs.native.example.rnchatkitauth0://wern.auth0.com/ios/org.reactjs.native.example.RNChatkitAuth0/callback

The Android callback URL follows the format:

    {YOUR_APP_PACKAGE_NAME}://{YOUR_AUTH0_DOMAIN}/android/{YOUR_APP_PACKAGE_NAME}/callback

While iOS follows the format:

    {YOUR_BUNDLE_IDENTIFIER}://${YOUR_AUTH0_DOMAIN}/ios/{YOUR_BUNDLE_IDENTIFIER}/callback

The name of the React Native project that you’ll be creating later is RNChatkitAuth0. Note that the app’s name in the second instance for the bundle identifier above uses the same letter case as your project folder (RNChatkitAuth0 as opposed to rnchatkitauth0).

Next, go to ConnectionsSocial and enable Google and Facebook:

Auth0 has dedicated documentation on how to configure the two, but at the time of writing this tutorial, it’s not really updated with the recent UI changes:

In the next two sections, I’ll be showing you how to configure the two.

Setting up Google login

The first step is to go to console.developers.google.com. By default, this will open a project that you’ve previously configured. In that case, you can probably skip this section because you already know what you’re doing. The goal of this section is to create an OAuth client ID.

If you don’t already have one then keep reading.

Click on the Select a project button on the upper left corner of the screen. This should open a modal which shows the button for creating a new project:

Once the project is created, click on the burger menu on the upper left side of the screen, hover over APIs & Services then click on Credentials. That should show you the screen for creating credentials. Click on the Create credentials button and select OAuth client ID:

If you haven’t previously configured a consent screen, it should show the following. Just click on the Configure consent screen button:

On the screen that follows, input the application name (RNChatkitAuth0) then scroll down to the Authorised domains section. Add auth0.com to it, press enter, then click on the Save button.

Once that’s done, go back to creating the OAuth client ID by clicking on the Credentials tab. On the screen that shows up, select Web application for the application type, enter the name of the client (RNClient), and add your Auth0 login callback URL under the Authorized redirect URIs field. For me, it’s https://wern.auth0.com/login/callback. So you just have to replace wern.auth0.com with the domain assigned to your Auth0 account. You can find it on your app settings page. Once you’ve added it, click on the Create button:

If you’re wondering why we selected Web Application as the Application Type: this is because Auth0 will just open a browser tab to authenticate the user. It’s not really baked into the app itself just like a native Android or iOS app would be.

Once the OAuth client is created, it should show the client ID and client secret which you can paste into Auth0’s Google settings modal. Click SAVE to save the credentials or TRY to try logging in with your Google account using Auth0:

You should see a screen similar to the following if the authentication flow works:

Setting up Facebook

Go to developers.facebook.com to create an app. On that page, click on the Add a New App button. That will show the modal for creating a new app:

Once the app is created, it lets you select the scenarios in which you’re going to use it. Select Integrate Facebook Login:

Because you selected Facebook login, it will recommend you to go through the quick start for the Facebook login. Select Web and enter your Auth0 domain as the site URL.

Next, click on Continue and then click Next a bunch of times until you get to the end of it. At that point, you can click on the Settings menu on the left:

It should show the following. Here, you can add your Auth0 login callback URL. For me, it was https://wern.auth0.com/login/callback. Just change wern.auth0.com to the domain name assigned by Auth0 to your account:

Once that’s done, go to your app’s basic settings page and add auth0.com as an app domain and save the changes:

At this point, you can copy your app ID and app secret to the Auth0 connection for Facebook:

Be sure to check only the Public Profile because that’s the only one we need. Because at the time of writing this tutorial, there’s a problem with the integration of Facebook login on Auth0. The error looks something like this:

Unsupported get request. Object with ID ‘me’ does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https://developers.facebook.com/docs/graph-api

This has to do with the release of Facebook Graph API version 3.3.

The solution is to disable the Social Context in the Facebook settings on Auth0. This was previously checked by default, so if this is your first time creating an Auth0 app, it should already be unchecked by default.

Bootstrapping the app

To set up the starter app, clone the GitHub repo and switch to the starter branch:

    git clone http://github.com/anchetaWern/RNChatkitAuth0
    git checkout starter

After that, install the dependencies, re-create the android and ios folders, then link the native modules:

    yarn

    react-native eject

    react-native link react-native-auth0
    react-native link react-native-config
    react-native link react-native-device-info
    react-native link react-native-restart
    react-native link react-native-sensitive-info
    react-native link react-native-gesture-handler

Next, update the android/app/build.gradle file to include the config for the React Native Config package:

    apply from: "../../node_modules/react-native/react.gradle"

    apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" // add this

Lastly, update the .env and server/.env file with your Auth0 and Chatkit credentials:

    // .env
    AUTH0_DOMAIN="YOUR AUTH0 DOMAIN"
    AUTH0_CLIENT_ID="YOUR AUTH0 APP CLIENT ID"
    AUTHO_SCOPE="openid profile offline_access"
    AUTH0_AUDIENCE="https://YOUR_AUTH0_DOMAIN/userinfo"

    CHATKIT_INSTANCE_LOCATOR_ID="YOUR CHATKIT INSTANCE LOCATOR ID (omit v1:us1:)"
    CHATKIT_SECRET_KEY="YOUR CHATKIT INSTANCE SECRET KEY"

    // server/.env
    CHATKIT_INSTANCE_LOCATOR_ID="YOUR CHATKIT INSTANCE LOCATOR ID (omit v1:us1:)"
    CHATKIT_SECRET_KEY="YOUR CHATKIT INSTANCE SECRET KEY"

All of the config above are self-explanatory aside from the AUTH0_SCOPE and AUTH0_AUDIENCE so I’ll walk you through those briefly:

  • AUTH0_SCOPE - used for specifying which authentication protocol (openid), user data (profile), and privileges (offline_access) we want the user to provide us. offline_access allows us to retrieve the user’s refresh token. This token allows us to make a request for a new access token after the existing one expires. Note that this requires you to pass in a unique device ID (as you’ll see later).
  • AUTH0_AUDIENCE - this is the Auth0 endpoint which returns the user’s data.

Updating the app

Now we’re ready to update the app. In this section, we will integrate social login with Auth0 and create a corresponding Chatkit user for the user who logged in. That user will then be used when they log in in the future.

The first thing that you need to do is update the Login screen so it uses the packages we installed earlier:

    // src/screens/Login.js
    import Auth0 from "react-native-auth0"; // for using Auth0 within React Native
    import DeviceInfo from "react-native-device-info"; // for getting unique device ID
    import SInfo from "react-native-sensitive-info"; // for securely storing the access and refresh tokens returned by Auth0
    import RNRestart from "react-native-restart"; // for restarting the app after acquiring a new access token
    import { NavigationActions, StackActions } from "react-navigation"; // for implementing navigation

Next, we initialize Auth0:

    const auth0 = new Auth0({
      domain: Config.AUTH0_DOMAIN,
      clientId: Config.AUTH0_CLIENT_ID
    });

Once the component is mounted, we try to get the access token from the secure storage. If there’s an access token, we try to log in the user with it. The loginUser() method is responsible for connecting the user to Chatkit and navigating to the Rooms screen. The second argument that we’ve passed is the function to execute when the access token passed as the first argument has already expired:

    componentDidMount() {
      SInfo.getItem("accessToken", {}).then((accessToken) => {
        if (accessToken) {
          this.loginUser(accessToken, this.refreshAccessToken);
        } else {
          this.setState({
            hasInitialized: true // hide the loader
          });
        }
      });
    }

Next, update the code for logging the user in so it uses Auth0. auth0.webAuth.authorize() will open the user’s default browser and ask them to log in with the providers we set up earlier. Once the user has successfully logged in, Auth0 returns an access token and refresh token.

After that, we call the loginUser() method and securely store the user tokens. This way, we can still request their details the next time they open the app:

    login = async () => {
      try {
        const { accessToken, refreshToken } = await auth0.webAuth.authorize({
          scope: Config.AUTHO_SCOPE,
          audience: Config.AUTH0_AUDIENCE,
          device: DeviceInfo.getUniqueID(), // required if you want to retrieve the refresh token
          prompt: "login"
        });

        this.loginUser(accessToken);

        // keep the tokens for later access
        SInfo.setItem("accessToken", accessToken, {});
        SInfo.setItem("refreshToken", refreshToken, {});

      } catch (auth0LoginError) {
        console.log('error logging in: ', auth0LoginError);
      }
    }

Here’s the code for the loginUser() method. This accepts the access token and the function to execute if the access token has already expired. We then use the access token to request for the user’s info which we then use for creating a Chatkit user (if it doesn’t already exist). The sub property contains the unique user ID for connecting to Chatkit:

    loginUser = (accessToken, errorCallback) => {
      auth0.auth
        .userInfo({ token: accessToken })
        .then(async (userData) => {
          try {
            await axios.post(`${CHAT_SERVER}/create-user`, userData);

            const chatManager = new ChatManager({
              instanceLocator: CHATKIT_INSTANCE_LOCATOR_ID,
              userId: userData.sub,
              tokenProvider: new TokenProvider({ url: CHATKIT_TOKEN_PROVIDER_ENDPOINT })
            });

            const currentUser = await chatManager.connect();
            this.currentUser = currentUser;

            this.goToRoomsPage({id: userData.sub, currentUser: this.currentUser});

          } catch (chatManagerError) {
            console.log("error connecting to Chat Manager: ", chatManagerError);
          }
        })
        .catch(errorCallback);
    }

Here’s a sample response data we get from the auth0.auth.userInfo() call:

    {
      "sub":"google-oauth2|UNIQUE_GOOGLE_ACCOUNT_ID",
      "givenName":"Shu",
      "familyName":"Takada",
      "nickname":"takada.shu",
      "name":"Shu Takada",
      "picture":"URL TO PROFILE PICTURE",
      "gender":"male",
      "locale":"en-GB",
      "updatedAt":"2019-05-13T04:00:56.920Z"
    }

Here’s the goToRoomsPage() method. This accepts the object containing the user’s ID and the reference to the current Chatkit user as its argument because we need to pass those to the next page. But instead of the navigating to the next page using this.props.navigation.navigate(), we have to use the StackActions to reset the stack. This is because we don’t want the user to be manually going back to the Login screen after they got authenticated. That should only happen when they choose to log out:

    goToRoomsPage = ({ id, currentUser }) => {
      // navigate to the Rooms page and treat it as the very first page
      const resetAction = StackActions.reset({
        index: 0,
        actions: [
          NavigationActions.navigate({
            routeName: "Rooms",
            params: {
              id,
              currentUser,
              auth0
            }
          })
        ]
      });

      this.props.navigation.dispatch(resetAction);
    }

Lastly, we have the refreshAccessToken() method. This gets executed if the access token that we securely stored has already expired. From the login code earlier, we stored a refresh token along with the access token. This token allows us to make a request for a new access token, and that’s exactly what we’re doing. Once a new access token is returned, we store it again then restart the app so that componentDidMount() fires again using the new access token:

    refreshAccessToken = () => {
      SInfo.getItem("refreshToken", {})
        .then((refreshToken) => {
          auth0.auth
            .refreshToken({ refreshToken: refreshToken })
            .then((newAccessToken) => {
              SInfo.setItem("accessToken", newAccessToken);
              RNRestart.Restart(); // restart so componentDidMount fires again with the new access token
            })
            .catch((newAccessTokenError) => {
              this.setState({
                hasInitialized: true
              });
              Alert.alert("Cannot refresh access token. Please login again.");
            });
        });
    }

Log out user

The authentication integration wouldn’t be complete without a logout sequence. In this section, we’ll be looking at how to do that.

First, open the src/screens/Rooms.js file and get the reference to auth0 that was passed from the Login screen:

    componentDidMount() {
      // ...

      this.currentUser = navigation.getParam("currentUser");
      this.auth0 = navigation.getParam("auth0"); // add this

      // ...
    }

Next, update the enterChat() function to pass auth0 as a navigation param:

    enterChat = async (room) => {
      this.props.navigation.navigate("Chat", {
        roomName: room.name,
        auth0: this.auth0 // add this
      });
    }

On the Chat screen, import the packages that we need for implementing the logout functionality:

    import { ActivityIndicator, View, TouchableOpacity, Text, Alert } from "react-native"; // add TouchableOpacity, Text, and Alert
    import { StackActions, NavigationActions } from "react-navigation"; // for navigating back to the login page
    import SInfo from "react-native-sensitive-info"; // for clearing the tokens we saved earlier

Next, inside the navigation header, add the button for logging the user out:

    static navigationOptions = ({ navigation }) => {
      const { params } = navigation.state;
      return {
        headerTitle: params.roomName,
        headerRight: (
          <View style={styles.headerRight}>
            <TouchableOpacity style={styles.headerButtonContainer} onPress={params.logoutUser}>
              <View>
                <Text>Logout</Text>
              </View>
            </TouchableOpacity>
          </View>
        )
      };
    }

In the constructor, extract the reference to auth0 that we passed from the Rooms page earlier:

    constructor(props) {
      // ...
      this.roomName = navigation.getParam("roomName");
      this.auth0 = navigation.getParam("auth0"); // add this
    }

Once the component is mounted, we set the logoutUser() function as an additional navigation param:

    componentDidMount() {
      this.props.navigation.setParams({
        logoutUser: this.logoutUser
      });

      // ...
    }

Lastly, add the code for logging the user out:

    logoutUser = async () => {
      // delete the tokens from the storage
      SInfo.deleteItem("accessToken", {});
      SInfo.deleteItem("refreshToken", {});
      try {
        await this.auth0.webAuth.clearSession(); // clear the browser session
      } catch (clearSessionError) {
        console.log("error clearing session: ", clearSessionError);
      }
      Alert.alert("Logged out", "You are now logged out");
      this.gotoLoginPage();
    }

Note: The auth0.webAuth.clearSession() function only works on iOS. This is because the Auth0 library cannot determine the browser used by the user to login in Android. It works in iOS because it only ever uses one browser to perform web-related actions inside an app.

Here’s the code for the gotoLoginPage() function:

    gotoLoginPage = () => {
      const resetAction = StackActions.reset({
        index: 0,
        actions: [
          NavigationActions.navigate({
            routeName: "Login"
          })
        ]
      });
      this.props.navigation.dispatch(resetAction);
    }

Updating the server

The final step is to add the route for creating the user on the server/index.js file. This will create the corresponding Chatkit user for the user who has logged in (if an account hasn’t been created already):

    app.post("/create-user", async (req, res) => {
      const { sub: id, name, picture: avatarURL } = req.body;  
      try {
        let user = await chatkit.createUser({
          id,
          name,
          avatarURL
        });
        res.send('ok');
      } catch (err) {
        if (err.error === "services/chatkit/user_already_exists") {
          res.send('ok');
        } else {
          let statusCode = err.error.status;
          if (statusCode >= 100 && statusCode < 600) {
            res.status(statusCode);
          } else {
            res.status(500);
          }
        }
      }
    });

Running the app

At this point, you can now run the server:

    cd server
    yarn start
    ~/.ngrok http 5000

Replace the CHAT_SERVER URL with your ngrok HTTPS URL on the src/screens/Login.js file and run the app:

    react-native run-android
    react-native run-ios

Conclusion

In this tutorial, you learned how to integrate Auth0 to your existing Chatkit chat app. Along the way, you also learned how to configure an auth client with Google and Facebook and added them as a login provider to Auth0.

You can find the code used in this tutorial on this GitHub repo.

Further reading:

React Hooks: Understanding The Weird Parts
https://morioh.com/p/e8a4604157cd

How to Creat a Dynamic select Menu with React
https://morioh.com/p/8db64ea09577

A beginner’s guide to writing good React code
https://morioh.com/p/0dc8259c513c

Functional vs Class Components in React - Everything you need to know
https://morioh.com/p/af19b4e52fd7

Suggest:

Build a Real Time Chat App With Node.js

Getting Started with Node.js - Full Tutorial

JavaScript for React Developers | Mosh

How To Create A Password Protected File Sharing Site With Node.js, MongoDB, and Express

Getting Closure on React Hooks

React Native Course for Beginners [Build Mobile Apps in React Native]