Sunday, July 31, 2016

Firebase Auth (V3) with React Native

TL;DR
To do auth Firebase api V3 your magic methods are: createUserWithEmailAndPassword() for account creation, signInWithEmailAndPassword() to sign in and onAuthStateChanged() to get the current user at app initialization. You can get the current user after app initialization by calling auth.currentUser.
Checkout the demo project in this repo.

Firebase V3

I recently started React Native development, after learning the basics and what problems it solves, I decided to try to integrate Firebase with it. Things went swimmingly for the first 30 minutes, until I tried to run the project. After making a project, installing Firebase and finding a recent tutorial from April, I discovered that the newest version of Firebase (v3) had just come out a little over a month ago. With the change in version, the auth mechanism in Firebase had been revamped so I began to dig through the docs to figure out how to manage authentication and users in my React Native app.

Well, a few weeks later and I have finally ironed out the details, we are going to cover account creation, login and remember me in this simple app for login. We will be using email and password auth, and building the app for android. The same approach can be used for iOS with some tweaking.

If you haven't already, make sure to set up React Native on your machine, Here are directions for setting up React Native on Windows and on Mac (for mac, just go through the intro and the Getting Started sections). I am also assuming that you have done some React or React Native development and are familiar with how components are written and what state and props are. If you haven't you can still try out the tutorial but it may prove a bit overwhelming.

Installation

First we will have to create our React Native project and install Firebase. Open your command line (as an administrator if you are in Windows) and navigate to the directory where you want to place your project. Then create your project by running:

react-native init ProjectName

Now in the command line, navigate to the root of your new project and install Firebase:

cd ProjectName
npm install firebase --save
npm install

Go ahead and start an emulator (or connect your device to the computer) and run the project:

react-native run-android

At this point you should have a basic app running on your emulator.

Setup Firebase Account

Firebase is now owned by google so setting up an account takes no effort if you have an existing google account. Go to the Firebase page and follow the "Sign in" link at the top right of the screen.


After logging in with your google credentials, follow the "Go to Console" link at the top right of the Firebase home page. In the console you will be able to manage and create new projects. Click  the "Create new project" button on the screen and enter a name.

After creating your new Firebase project you will have to add an auth method. In the left panel click on "Auth". In the auth panel go to the "Sign-in Method" tab and enable the Email/Password option.


Then click on the name of your project in the top left of the side panel, and click the button to "Add Firebase to your web app". That should open a modal with code to initialize your Firebase project.


Open the index.android.js file from the root of your project directory in your favorite React IDE (I prefer Atom with the Nuclide plugin from facebook). Copy the code from the modal and paste it into your index.android.js, remove the script tags and import the Firebase library instead:

// other imports
import * as firebase from 'firebase';

const firebaseConfig = {
  apiKey: "GENERATED_API_KEY",
  authDomain: "generatedAuthDomain.firebaseapp.com",
  databaseURL: "https://geenratedDbUrl.firebaseio.com",
  storageBucket: "generatedBucket.appspot.com",
};
const firebaseApp = firebase.initializeApp(firebaseConfig);

// other code and class declaration.

Refresh your emulator to make sure your code still compiles (you shouldn't see any changes yet in the UI). You can refresh the emulator by tapping "r-r" on windows or "cmd-r" in mac. Next we will make it possible to make new users with Firebase in our app.

Account Creation

We will first have to create the Account creation page, this will be a React component that defines the appearance of the screen. Before we start making components and pages let's setup a folder structure for our project. In the root of your project make a "src" directory with "styles" and "pages" sub directories.


In the styles folder we will create a file for all of our common styles: baseStyles.js.

In the pages folder create a Signup.js file (case sensitivity matters, I originally named it signup.js which broke the project), in here we are going to display a simple UI with a username and password field and button. When a user clicks the button we will make a call to Firebase to create the new user. We will then change the UI based on the response from the server with the use of the promise returned by createUserWithEmailAndPassword().

Now that we have our signup page we need to navigate to it when the app starts. In your index.android.js remove your autogenerated styles variable and replace the render method with a Navigator that will take us to our Signup page when the app starts. Having our signup page isn't necessary right now, but it will prove handy as we add more logic to our app.

Go ahead and run your project, you should see a screen like the one below:


Try some combinations of invalid credentials, you should see dialogs appear with the error message from Firebase. Then try a pair of valid credentials, you should get a dialog message informing you of successful account creation.

Login

Great, now we have out users signing up for our app, but we will also need to let them log into our app after they have an account. To do this we will be creating a login page with layout similar to the signup page and add another button to switch between the two pages.

Inside of the pages directory create a new file, Login.js. This will have a layout very similar to our signup page. However, when a user taps on the login button we will instead try to authenticate and display a message based on the result. In addition we will have a button that takes the user to the signup page.

In addition we will have our Navigator default the user to the Login, change the initialRoute to go the the Login component instead of Signup, and import the Login page at the top of the file:

// imports
import Login from './src/pages/Login';
//.... inside of render()
      <Navigator
        initialRoute={{component: Login}}
        configureScene={() => {
          return Navigator.SceneConfigs.FloatFromRight;
        }}
//...

While we are at it, let's add button to the signup page to take the user back to the login page:

// imports
import Login from './src/pages/Login';
//.... Inside of render() we will add a button
    const content = this.state.loading ? <ActivityIndicator size="large"/> :
      <View>
      ...
        </TouchableHighlight>
        <!-- add this new touchable wrapper -->
        <TouchableHighlight onPress={this.goToLogin.bind(this)} style={styles.transparentButton}>
          <Text style={styles.transparentButtonText}>Go to Login</Text>
        </TouchableHighlight>
      </View>;
//... And finally we will add a goToLogin() method
  goToLogin(){
    this.props.navigator.push({
      component: Login
    });
  }

If you want to see the diff in github you can look at it here.

If we wanted to get a bit fancy we could take the password, username and button components and put them in their own stand-alone component, but in this tutorial we will forgo that.

Go ahead and run your project, you should be able to navigate between the signup and login page and use your created credentials to "login".

Display Account Details

Now that we can log users in, lets go ahead and create a page that will display their info. If the user is logged in then they will automatically be redirected to this page on app start up.

Let's go ahead and create a new page for the user's account info. In the pages directory go ahead and make an Account.js file. We have a few custom styles for this page declared at the start, after that the interesting things to note are the render() method, which loads the user's details and displays a log out button, and the logout method which signs out the user and sends them to the login screen.

Now in the login page we need to redirect the user to the account page after they log in. In Login.js remove the "Login Succesful" alert from the login() method and instead navigate the user to the account page:

// imports
import Account from './Account';
//.... inside of render() navigate the user to the account screen.
      ).then((userData) =>
      {
        this.setState({
  loading: false
});
        this.props.navigator.push({
          component: Account
        });
      }
//...

Go ahead and refresh the app, when you log in now you should be redirected to the accounts page. If you hit the logout button you will be returned to the login page. You can see the diff of this section in github here.

Remember Me

If you kill and restart the app you will notice that you will be returned to the login page instead of the accounts page. This can be frustrating for users since one mobile you expect to be kept logged in until you sign out. With all of our groundwork laid out, all we have to do for remember me is check the user in Firebase and redirect the app to the account screen if they are logged in. 

To do this we will revisit our index.android.js file. We now add a "page" field to our state which will determine which page we navigate to. In componentWillMount() we wait for Firebase to check the auth state using onAuthStateChanged(). This takes in a function (observer) that will be called when the auth state changes. In our case we just want to go to the accounts screen if they user is not null and go to the login screen if it is. Because of the number of changes to the index.android.js I am instead going to link you to the github diff so you can easily see what was added to support remember me.

Closing thoughts and Docs

After trying the auth mechanism in the V2 version of the api and V3 I do like the extent to which Firebase now manages the user for you. In the tutorial I had tried before I had save the user to Async Storage myself after they logged in. In addition, the use of promises does seem syntactically cleaner.

If you are looking for more to do with Firebase and react native check out this tutorial I wrote for creating a React Native app with a ListView backed by Firebase.

Please leave comments with any issues, questions or feedback you have.

Check out the full V3 docs for Firebase here.
And here is a link to the github repo with the sample project.

12 comments:

  1. Excellent, your post help me a lot!!!
    Does React native support Firebase social login?
    Thanks

    ReplyDelete
  2. Hey Gustavo, thank you for the feedback! It's great to know that the post is helpful.

    I did some digging and it looks like you can implement social login (at least for Facebook, Twitter and google) with react native. Note that I haven't tried this out myself but here is a rundown of information to get you started:

    With the 3.1 sdk release, the Firebase team announced that while you can't use "headful" methods to login (which would rely on a redirect or popup) "You can still sign in or link with a federated provider by using signInWithCredential() with an OAuth token from your provider of choice" [1]. The only trick is getting that token!

    If any of these providers (facebook, google, twitter, etc) have React Native login components then your job will be possible. You can import said components, follow their docs and use them to get your credentials. I have included links below to the projects for login with facebook, google and twitter below [2], [3], [4]. In addition, I managed to find one SO post where someone describes (and includes some code) for how to do social login with facebook [5]. These libraries can be a bit more work than you'd first think to integrate because they rely on native code to be able to do auth.

    In addition to those libraries there are also some alternatives that may be easier (but more limited). You could try using a library that supports auth for multiple platforms: like react-native-social-auth that supports facebook and twitter [6] and react-native-simple-auth which supports more but only on iOS [7]. There are probably more that I am missing that you could find one that fits for you with some research.

    If you need more inspiration for how to handle the credentials then take a look at the different *****-credentials.html files for the different providers in Firebase's example repo for its js sdk[8]. While most of the html code is useless the js code for getting the credentials and sending them to Firebase will be almost identical to how you do it in react native.

    Please share what comes of this, and if you get a chance please make a post and link it here and on SO for others to learn! Unfortunately I don't expect I'll have time to try this out myself (sounds like it would be cool to do) but I wish you the best, let me know if you have any more questions.

    [1] https://firebase.googleblog.com/2016/07/firebase-react-native.html
    [2] https://github.com/facebook/react-native-fbsdk
    [3] https://github.com/devfd/react-native-google-signin
    [4] https://www.npmjs.com/package/react-native-twitter-signin
    [5] http://stackoverflow.com/questions/38175086/facebook-login-in-react-native-firebase-3-1
    [6] https://www.npmjs.com/package/react-native-social-auth
    [7] https://www.npmjs.com/package/react-native-simple-auth
    [8] https://github.com/firebase/quickstart-js/tree/master/auth

    ReplyDelete
  3. How to make to go directly to account page when I'm opening the application every time after the first time when I am log in ?

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. modify the signup method like this : // Make a call to firebase to create a new user.
    this.props.firebaseApp.auth().createUserWithEmailAndPassword(
    this.state.email,
    this.state.password).then(() => {
    // then and catch are methods that we call on the Promise returned from
    // createUserWithEmailAndPassword
    alert('Your account was created!');
    this.props.navigator.push({
    component: Account
    });

    ReplyDelete
  6. Thanks a lot for this, exactly what I was looking for.

    ReplyDelete
  7. This was a fantastic tutorial! Thank you for this

    ReplyDelete
  8. Navigator is deprecated and has been removed from this package.

    To fix this, follow these steps:

    npm install react-native-deprecated-custom-components

    then

    import NavigationExperimental from 'react-native-deprecated-custom-components';

    then

    replace <Navigator with

    <NavigationExperimental.Navigator

    ReplyDelete
  9. Thank you so much for this post!! I just started learning React Native and this tutorial helped me so much; especially with understanding how to incorporate Firebase into my project.

    However, I keep running into issues with the Navigator feature and linking my pages together (e.g. Login to Home; Login to Register). I used the code you provided above and I get "undefined is not an object (evaluating 'this.props.navigator')". I also run into issues with regards to the push function, which returns a similar error message.

    I installed react-native-deprecated-custom-components and imported and tried <NavigationExperimental.Navigator, but it did not recognize this component.

    Since I am still new to React Native and coding in general, I am wondering if anyone else encounters these issues; and if so, what possible solutions could I use to fix them.

    ReplyDelete