Developing our first iOS App with React Native (pt 2) - User Auth

In this post, I will be going through how we setup Authentication for our HireArt Mobile App. If you've missed our first post about why we chose React Native to develop our first iOS app, you can read about it here

Before jumping in, I just want to address how we're handling saving to the local device and how we handle API calls. We looked at a bunch of options including flux and redux, but found that they felt like overkill or didn't seem to solve our particular problem. So we went with our own approach of a super simple API/Store. The details of that implementation will be in another post, but for now we'll just assume that there's a module to handle API/Store which we can interact with, and the module is capable of making API calls to our Rails endpoints.

You'll notice right away that this appears to break React's uni-directional flow of data. However, I assure you that it does not. When a view calls an action, that action may interact with the store, server, or both. After which it can either update the state of the view that called it, store the return value to the device, or navigate to a different view. At no point does the Action return a value to the View.

We'll start with an overview of the views we use, the methods that support these views, and the react-native npm packages we use to make things easier.

Views

We have 4 views dedicated to authentication:

  1. Welcome.js - This is the main welcome page which conditionally displays login links if the user is not found/authenticated yet.

  2. Authentication.js - The Sign In/Sign Up form lives here along with the FB Login button

  3. FbRequirePassword.js - This view, if authenticated from FB and is an existing user, will prompt the user to enter their HireArt password to link to their FB account.

  4. ForgotPassword.js - For users who forgot their passwords. Prompts them to enter their email.

Methods

We've implemented 3 methods to make our authentication work:

  1. getUser() - Checks local storeage for a user object, returns user if it exists

  2. authenticate(userForm) - Invoked after SignIn or SignUp, will send the user info to our Rails endpoint. If it gets a valid user in return, it calls HandleAfterAuth() with the user.

  3. handleAfterAuth(user) - Handles navigation after successful authentication. There are three possible outcomes:

    • Existing User - navigate to Dashboard
    • New User - navigate to Onboarding
    • Facebook Login for Existing User - navigate to FbRequirePassword to link their FB account to their existing HA account.

NPM Packages

  1. tcomb-form-native - really helpful in building forms with some basic validation and keyboard controls. https://www.npmjs.com/package/tcomb-form-native

  2. react-native-router-flux - Greatly simplified navigation. https://github.com/aksonov/react-native-router-flux

  3. react-native-simple-store - Saving and loading user data to the device was super easy with Simple Store. https://www.npmjs.com/package/react-native-simple-store

Overall Diagram

Now that we have a broad overview of the system, I've put together a diagram that shows how it all comes together.

Here is an overly complicated diagram showing our authentication process:

The dotted lines indicate view navigation sequence, blues are JS modules, green indicates servers.

Load User

Using Simple Store, this is quite trivial, just look for the user and if it exists, check to see if it has a valid auth token.

Store.get('user').then((user)=> {
  this._isUserValid(user)
})

Sign Up/Sign In Form

In the even there is no user, we setup a form using tcomb form

requiring tcomb form

var t = require('tcomb-form-native')

setting up form structure

var signInUser = t.struct({
  email: t.Str,
  password: t.Str,
})

var signUpUser = t.struct({
  first_name: t.Str,
  last_name: t.Str,
  email: t.Str,
  password: t.Str,
})

setting form options and keyboard restrictions

var options = {
  auto: 'placeholders',
  stylesheet: LoginFormStyle,
  fields: {
    password: {
      password: true,
      secureTextEntry: true,
    },
    email: {
      keyboardType: 'email-address',
      autoCorrect: false,
      autoCapitalize: false,
    },
    first_name: {
      autoCorrect: false,
    },
    last_name: {
      autoCorrect: false,
    },
  },
}

The form itself in render()

<Form
  ref="form"
  type={formType}
  options={options}
  value={this.state.formValue}
  onChange={this.onChange.bind(this)}
  />
<TouchableHighlight
  style={[Styles.buttonInset, {marginTop: 7}]}
  onPress={this.handleSubmit.bind(this)}
  underlayColor='#E3A90C'>
  <Text style={Styles.buttonText}>{submitText}</Text>
</TouchableHighlight>

When the form is submitted

handleSubmit() {
  var value = this.refs.form.getValue()
  if (value && this.passwordLengthCheck()) {
    this.setState({
      isLoading: true,
    })
    this._authenticate()
  } else {
    if (!this.passwordLengthCheck()) {
      AlertIOS.alert('Your password needs to be at least 8 characters long.')
    }
  }
}

Handling authentication

_authenticate(parent, user) {
  var obj = {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      user: user,
    }),
  }
  var url = // YOUR ENDPOINT
  fetch(url, obj)
    .then((res)=> {
      return res.json()
    }).then((json)=> {
      _handleAfterAuthenticate(parent, json, user)
    }).catch((error) => {
      console.warn(error)
    })
    .done()
}

The handleAfterAuthenticate() method simply saves the user data to the Store and decides which view to show next and navigates to it: Dashboard if existing user, OnBoarding if new user.

Facebook Login Integration

We used react-native-facebook-login which worked great. It took a few tries to get it setup properly and there are a lot of steps which I won't repeat here. Just make sure you follow every step and if it didn't work, start from the beginning.

Thanks for reading and keep an eye out for my next entry on Fetch/API!


Tom Tang leads mobile development at HireArt. Feel free to reach out: tom@hireart.com