Authentication in a React Native App Using AWS Amplify

Zeb Girouard

In this article, we will learn how to provide authentication to a React Native application, using AWS Amplify.

AWS Amplify is a declarative API for all of the services in the AWS suite. Amplify simplifies the setup for an AWS application with the Amplify CLI which allows you to create an AWS application locally and connect it to all of AWS’ services. It also simplifies the integration between these services.

MyAlligatorFace Demo App

Here at Alligator.io, we want to help people stay connected, and to that end, we are debuting a social networking app called MyAlligatorFace 😉🐊.

In Part 1 of this React Native walkthrough, we started the app with two navigation screens, Friends and Home and looked at React Navigation.

In Part 2, we connected MyAlligatorFace to the Redux state management library.

At the moment, we have two screens in MyAlligatorFace: on the Home page, we see how many friends we have and on the Friends page we can add new friends.

In this article, we will add a new screen for Authentication. This will contain a SignUp and LogIn component that we can toggle between.

Finally, we’ll save authentication information into a User object in Redux.

Getting Started with Authentication

Before we add authentication to our project, we need to spin up our current version of MyAlligatorFace.

Run the following commands in your Terminal:

$ git clone https://github.com/alligatorio/MyAlligatorFace.git --branch redux
$ cd MyAlligatorFace
$ git checkout -b auth
$ npm install
$ npm start

Open a React Native simulator (type i for iOS in the Terminal, a for Android), and navigate between the Home and Friends pages by clicking the Add some friends and Back to home links.

Now we can integrate AWS Amplify into our project so we can add authentication to our app.

AWS Amplify Setup

Amazon documents the setup for creating an AWS Amplify project here.

First, sign up for a free AWS account.

Then, install the AWS Amplify CLI package globally:

$ npm install -g @aws-amplify/cli

Then use the Amplify CLI package to configure your system:

$ amplify configure

You will be prompted to provide the AWS user credentials from your AWS free tier account.

The CLI will prompt you for several details about the project and redirect you to the web console to setup an IAM user. Accept all the default values in the prompts and web console.

Once setup is finished, return to the Amplify Getting Started page and click the Get Started button under React Native. Skip to Step 2. Install Amplify, since we’ve already completed the other steps.

Make sure you are in the MyAlligatorFace directory, then initialize it as an AWS Amplify project:

$ npm install --save aws-amplify aws-amplify-react-native
$ amplify init

Choose your default text editor, choose javascript as your project type, choose react-native for your framework, keep the default / as the source and distribution directory path, and accept the defaults for build and start scripts.

Now, add authentication to your Amplify project and keep the default configuration:

$ amplify add auth

Finally, update your configuration and push the authentication feature up to AWS:

$ amplify push

Keep all the default configuration.

Integrating Amplify Authentication into Our App

Amazon also documents the key setup steps for Authentication with Amplify.

First, in App.js, add the following lines:

App.js

import Amplify from 'aws-amplify';
import aws_exports from './aws-exports';

// ...

Amplify.configure(aws_exports);

// ...

Make sure all of this comes before your App class declaration, so Amplify can be used everywhere in your application.

This connects all of the setup we just did to our React Native project. Next, we will add User Sign-up.

Sign-up with Amplify

The first thing we’ll need to do is add an Authentication page. Create the following file at the root level of your project:

Authentication.js

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default class Authentication extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Welcome to MyAlligatorFace!</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Now, we need to add this screen to the app navigator. We can’t really show someone’s friends if we don’t know who they are, so we will add this Authentication screen to the top of the navigator to make it the first place users land:

AppNavigator.js

import { createStackNavigator } from 'react-navigation';
import Home from './Home';
import Friends from './Friends';
import Authentication from './Authentication';

const AppNavigator = createStackNavigator({
  Authentication: { screen: Authentication },
  Home: { screen: Home },
  Friends: { screen: Friends},
});

export default AppNavigator;

When our app refreshes, we should see this:

Blank Authentication Screen


Sign-up Form

In order to sign up, a user will have to insert their email and password into a form. To help with this, first install the react-native-elements library.

$ npm install --save react-native-elements@">=1.0.0-beta"

At the time of this writing, React Native Elements was on its seventh Beta version, so soon you can just use `npm install --save react-native-elements` to install the package.

If you would like to know more about React Native Elements, you can look at this post about React Native UI Tools. For now, though, suffice it to say that React Native Elements makes creating a form with inputs much easier.

Below is the Sign-up form we will use to add a new user to our AWS user pool. We’ll need an email field, a password field, and a confirm password field. Add the following code to Authentication.js:

Authentication.js

import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { Input, Button } from "react-native-elements";

export default class Authentication extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        Welcome to MyAlligatorFace!
        <Input
          label="Email"
          leftIcon={{ type: "font-awesome", name: "envelope" }}
          placeholder="my@email.com"
        />
        <Input
          label="Password"
          leftIcon={{ type: "font-awesome", name: "lock" }}
          placeholder="p@ssw0rd123"
          secureTextEntry
        />
        <Input
          label="Confirm Password"
          leftIcon={{ type: "font-awesome", name: "lock" }}
          placeholder="p@ssw0rd123"
          secureTextEntry
        />
        <Button title="Submit" />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center"
  }
});

Our auth screen is starting to come together:

Authentication Form


However, this form is not connected to AWS yet. In order to send the Sign-up information to AWS, we need to save it into the app’s state.

Authentication.js

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Input, Button } from 'react-native-elements';

export default class Authentication extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: '',
      password: '',
      confirmPassword: '',
    };
  }

  handleSignUp = () => {
    // Show the current state object
    alert(JSON.stringify(this.state));
  }

  render() {
    return (
      <View style={styles.container}>
        <Text>Welcome to MyAlligatorFace!</Text>
        <Input
          label="Email"
          leftIcon={{ type: 'font-awesome', name: 'envelope' }}
          onChangeText={
            // Set this.state.email to the value in this Input box
            (value) => this.setState({ email: value })
          }
          placeholder="my@email.com"
        />
        <Input
          label="Password"
          leftIcon={{ type: 'font-awesome', name: 'lock' }}
          onChangeText={
            // Set this.state.password to the value in this Input box
            (value) => this.setState({ password: value })
          }
          placeholder="p@ssw0rd123"
          secureTextEntry
        />
        <Input
          label="Confirm Password"
          leftIcon={{ type: 'font-awesome', name: 'lock' }}
          onChangeText={
            // Set this.state.confirmPassword to the value in this Input box
            (value) => this.setState({ confirmPassword: value })
          }
          placeholder="p@ssw0rd123"
          secureTextEntry
        />
        <Button
          title='Submit'
          onPress={ this.handleSignUp }
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Click Submit and you should see an alert like the one below:

Authentication Alert


Signing up with our user

Now, we’re ready to send the user information to AWS.

However, it should be noted that AWS by default requires a user to be confirmed with a Confirmation Code. This way, we can make sure that our social media enterprise is not bombarded with fake users or robots. So we’ll need to add AWS’s signUp method, but we’ll also need to add the confirmSignUp method.

Until we get all of this hooked up, avoid hitting the Submit button because we can easily create a user that is not yet confirmed, and waste time deleting or manually editing that AWS user.

Once we have performed the initial Sign-up, AWS will send a confirmation code to the email we used to sign up. So the first thing we need to do is create a modal where we can enter this confirmation code.

Authentication.js

import React from 'react';
import { StyleSheet, Text, View, Modal } from 'react-native';
import { Input, Button } from 'react-native-elements';

export default class Authentication extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: '',
      password: '',
      confirmPassword: '',
      confirmationCode: '',
      modalVisible: false,
    };
  }

  handleSignUp = () => {
    // Show the current state object
    alert(JSON.stringify(this.state));
  }

  render() {
    return (
      <View style={styles.container}>
        <Text>Welcome to MyAlligatorFace!</Text>
        <Input
          label="Email"
          leftIcon={{ type: 'font-awesome', name: 'envelope' }}
          onChangeText={
            // Set this.state.email to the value in this Input box
            (value) => this.setState({ email: value })
          }
          placeholder="my@email.com"
        />
        <Input
          label="Password"
          leftIcon={{ type: 'font-awesome', name: 'lock' }}
          onChangeText={
            // Set this.state.email to the value in this Input box
            (value) => this.setState({ password: value })
          }
          placeholder="p@ssw0rd123"
          secureTextEntry
        />
        <Input
          label="Confirm Password"
          leftIcon={{ type: 'font-awesome', name: 'lock' }}
          onChangeText={
            // Set this.state.email to the value in this Input box
            (value) => this.setState({ confirmPassword: value })
          }
          placeholder="p@ssw0rd123"
          secureTextEntry
        />
        <Button
          title='Submit'
          onPress={ this.handleSignUp }
        />
        <Modal
          visible={this.state.modalVisible}
        >
          <View
            style={styles.container}
          >
            <Input
              label="Confirmation Code"
              leftIcon={{ type: 'font-awesome', name: 'lock' }}
              onChangeText={
                // Set this.state.confirmationCode to the value in this Input box
                (value) => this.setState({ confirmationCode: value })
              }
            />
            <Button
              title='Submit'
              onPress={ this.handleConfirmationCode }
            />
          </View>
        </Modal>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Now, add the AWS signUp logic. Once we get a successful response from AWS, we’ll open the Modal we just created so the user can enter the Confirmation Code.

Authentication.js

import React from 'react';
import { StyleSheet, Text, View, Modal } from 'react-native';
import { Input, Button } from 'react-native-elements';
import { Auth } from 'aws-amplify';

// ...

handleSignUp = () => {
  // alert(JSON.stringify(this.state));
  const { email, password, confirmPassword } = this.state;
  // Make sure passwords match
  if (password === confirmPassword) {
    Auth.signUp({
      username: email,
      password,
      attributes: { email },
      })
      // On success, show Confirmation Code Modal
      .then(() => this.setState({ modalVisible: true }))
      // On failure, display error in console
      .catch(err => console.log(err));
  } else {
    alert('Passwords do not match.');
  }
}

// ...

Finally, add the AWS confirmSignUp logic. In addition to sending the request to AWS to make sure the Confirmation Code is valid, we need to dismiss the Modal, and navigate the user to the Home screen.

Authentication.js

import React from 'react';
// ...

handleSignUp = () => {
  // ..
}

handleConfirmationCode = () => {
  const { email, confirmationCode } = this.state;
  Auth.confirmSignUp(email, confirmationCode, {})
    .then(() => {
      this.setState({ modalVisible: false });
      this.props.navigation.navigate('Home')
    })
    .catch(err => console.log(err));
}

// ...

Our Sign-up code is complete! Now, head to your Cognito User Pools in AWS. If you changed from the default region (us-east-1) during the AWS setup, you may need to change the URL link to match your region.

You should see something like this in your web browser:

AWS Cognito Pools


Click the user pool and then click Users and groups on the left side of the screen. You should see we have no users…yet.

No Users Found


Now go back to your simulator and sign up with your first user:

Make sure you use an email that you have access to for Sign-up. You must be able to retrieve your Confirmation Code. Also, AWS requires passwords with at least one lowercase letter, one uppercase letter, one number, one special character, and at least 8 characters total.

Signup Step 1


Then enter the confirmation code you received in your email. It should be 6 digits long.

Signup Step 2


When you click Submit, you should be directed to the Home screen.

Signup Step 3


Now look at your Cognito Pool again, and you should see your new user!

First User Created


Users can now sign up to MyAlligatorFace. Next, we will add the ability to sign-in for existing users.

Sign-in with Amplify

Before we implement Sign-in with Amplify, we have to create a Sign-in form. This will look a lot like the Sign-up form, just without the password confirmation. Once we have both forms, we will need a button that can toggle between them. Finally, we can add the Sign-in logic that will be similar to the way we added Sign-up logic.

Sign-in Form

Add the following code below the Sign-up “Submit” Button:

Authentication.js


// ...

<Input
  label="Email"
  leftIcon={{ type: 'font-awesome', name: 'envelope' }}
  onChangeText={
    // Set this.state.email to the value in this Input box
    (value) => this.setState({ email: value })
  }
  placeholder="my@email.com"
/>
<Input
  label="Password"
  leftIcon={{ type: 'font-awesome', name: 'lock' }}
  onChangeText={
    // Set this.state.email to the value in this Input box
    (value) => this.setState({ password: value })
  }
  placeholder="p@ssw0rd123"
  secureTextEntry
/>
<Button
  title='Submit'
  onPress={ this.handleSignIn }
/>

// ...

When you add this form, you will get a pretty full Authentication page:

Two Forms


Now, let’s add a toggle so we only see one of these forms at a time.

Toggling Sign-up and Sign-in

In order to show only the Sign-up form or the Sign-in form, we’ll use a component from react-native-elements called the ButtonGroup. We will have a Sign Up and Sign In button that will toggle which form to show by changing a variable in this.state.

First, import the ButtonGroup component from react-native-elements:

Authentication.js

import React from 'react';
import { StyleSheet, Text, View, Modal } from 'react-native';
import { Input, Button, ButtonGroup } from 'react-native-elements';
import { Auth } from 'aws-amplify';

Next, add our ButtonGroup below the “Welcome to MyAlligatorFace!” Text component:

Authentication.js

// ...
<ButtonGroup
  onPress={this.updateIndex}
  selectedIndex={this.state.selectedIndex}
  buttons={ this.buttons }
/>
// ...

Now we need to define updateIndex, selectedIndex, and buttons which will tell our Authentication component which form is selected.

Authentication.js

// ...
constructor(props) {
  super(props);
  this.state = {
    email: '',
    password: '',
    confirmPassword: '',
    confirmationCode: '',
    modalVisible: false,
    selectedIndex: 0,
  };

  this.buttons = ['Sign Up', 'Sign In']
}

updateIndex = () => {
  // If selectedIndex was 0, make it 1.  If it was 1, make it 0
  const newIndex = this.state.selectedIndex === 0 ? 1 : 0
  this.setState({ selectedIndex: newIndex })
}

// ...

Finally, we need to use a ternary, based on selectedIndex to show the relevant form (Sign Up or Sign In). Make the following change to the code below the ButtonGroup:

Authentication.js

// ...

{ this.state.selectedIndex === 0 ? (
  <View>
    <Input
      label="Email"
      leftIcon={{ type: 'font-awesome', name: 'envelope' }}
      onChangeText={
        // Set this.state.email to the value in this Input box
        (value) => this.setState({ email: value })
      }
      placeholder="my@email.com"
    />
    <Input
      label="Password"
      leftIcon={{ type: 'font-awesome', name: 'lock' }}
      onChangeText={
        // Set this.state.email to the value in this Input box
        (value) => this.setState({ password: value })
      }
      placeholder="p@ssw0rd123"
      secureTextEntry
    />
    <Input
      label="Confirm Password"
      leftIcon={{ type: 'font-awesome', name: 'lock' }}
      onChangeText={
        // Set this.state.email to the value in this Input box
        (value) => this.setState({ confirmPassword: value })
      }
      placeholder="p@ssw0rd123"
      secureTextEntry
    />
    <Button
      title='Submit'
      onPress={ this.handleSignUp }
    />
  </View>
) : (
  <View>
    <Input
      label="Email"
      leftIcon={{ type: 'font-awesome', name: 'envelope' }}
      onChangeText={
        // Set this.state.email to the value in this Input box
        (value) => this.setState({ email: value })
      }
      placeholder="my@email.com"
    />
    <Input
      label="Password"
      leftIcon={{ type: 'font-awesome', name: 'lock' }}
      onChangeText={
        // Set this.state.email to the value in this Input box
        (value) => this.setState({ password: value })
      }
      placeholder="p@ssw0rd123"
      secureTextEntry
    />
    <Button
      title='Submit'
      onPress={ this.handleSignIn }
    />
  </View>
) }

// ...

If we open our app now, we can now toggle between Sign-Up and Sign-In. This is exactly what we wanted, except for one thing… Our form is now squished into a tiny section of the middle of our screen. To fix this, we need to create a style for our new View components for our forms, then apply them:

Authentication.js

// ...

{ this.state.selectedIndex === 0 ? (
  <View style={styles.form}>
    <Input

// ...

) : (
  <View style={styles.form}>
    <Input

// ...

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    form: {
     width: '90%',
    }
});

This gives us a much better-looking form:

Better Forms


Now, we just need to add the AWS signIn function, and we’re ready to sign in with the user we created in the Sign-up section.

Signing in with our user

Just like we added the signUp function, add the signIn function from AWS to our handleSignIn handler for the “Sign In” Submit button:

Authentication.js

// ...

handleSignIn = () => {
  const { email, password } = this.state;
  Auth.signIn(email, password)
    // If we are successful, navigate to Home screen
    .then(user => this.props.navigation.navigate('Home'))
    // On failure, display error in console
    .catch(err => console.log(err));
}

handleSignUp = () => {

// ...

Now, sign-in with the user you created in the Sign-up section, and you should be directed to the Home screen:

Home Screen


Now we can sign up and sign in to MyAlligatorFace. We only have one more thing to add, before we have a full Authentication suite for our app: Sign-out.

Sign-out with Amplify

The excellent news about Sign-out with Amplify is that it’s much easier to implement than Sign-up or Sign-in. Before we dive into that, though, we should briefly talk about how Amplify actually handles authentication and authorization.

The central concept behind Amplify’s Auth is the JSON Web Token, or JWT. This method allows us to confirm quickly who our user is (Authentication) and what they can do (Authorization) in a simple, encoded object. Once we sign in with Amplify, an AWS server sends us back a JWT that looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYWFhYWFhYS1iYmJiLWNjY2MtZGRkZC1nYXRvciIsImF1ZCI6Inh4eHh4eHh4eHh4eGdhdG9yIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNTAwMDA5NDAwLCJpc3MiOiJodHRwczovL2NvZ25pdG8taWRwLmFwLWVhc3QtMS5hbWF6b25hd3MuY29tL2FwLWVhc3QtMV9nYXRvciIsImNvZ25pdG86dXNlcm5hbWUiOiJhbGxpZSIsImNvZ25pdG86Z3JvdXBzIjpbInV3LWFwcC1hZG1pbmlzdHJhdG9yIiwidXctYXBwLXVzZXIiXSwiZXhwIjoxNTAwMDEzMDAwLCJnaXZlbl9uYW1lIjoiQWxsaWUiLCJpYXQiOjE1MDAwMDk0MDAsImVtYWlsIjoiYWxsaWVAZ2F0b3IuY29tIn0.tPVJRRqJQ6ZI5fnomvR-TcOgqNGfU6FeNaBTZBogK3w

This is an encoded string that, when decoded, gives us the following object payload:

{
  "sub": "aaaaaaaa-bbbb-cccc-dddd-gator",
  "aud": "xxxxxxxxxxxxgator",
  "email_verified": true,
  "token_use": "id",
  "auth_time": 1500009400,
  "iss": "https://cognito-idp.ap-east-1.amazonaws.com/ap-east-1_gator",
  "cognito:username": "allie",
  "cognito:groups": [
    "uw-app-administrator",
    "uw-app-user"
  ],
  "exp": 1500013000,
  "given_name": "Allie",
  "iat": 1500009400,
  "email": "allie@gator.com"
}

This is stored locally on the mobile device, specifically in AsyncStorage inside the mobile app. When we sign out with AWS, this JWT is deleted. That’s all signOut needs to do. We delete the JWT, and bounce the user to the Authentication screen.

To do that, add the following function in Home.js:

Home.js

// ...

class Home extends React.Component {
  handleSignOut = () => {
    Auth.signOut()
      .then(() => this.props.navigation.navigate('Authentication'))
      .catch(err => console.log(err));
  }

  render() {

// ...

We also need to import the Amplify Auth library. And while we’re at it, let’s use the cleaner-looking Button from react-native-elements:

Home.js

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Button } from 'react-native-elements';
import { connect } from 'react-redux';
import { Auth } from 'aws-amplify';

And finally, we add the “Sign Out” button at the bottom of the screen:

Home.js

// ...
      <Button
        title="Add some friends"
        onPress={() =>
          this.props.navigation.navigate('Friends')
        }
      />
      <Button
        title="Sign Out"
        onPress={this.handleSignOut}
      />
    </View>
// ...

Now, return to the app, and sign in. You will be directed to the Home screen:

Home Screen


And that’s it! We can now sign up, sign in, and sign out with MyAlligatorFace.

Conclusion

In this article, we covered:

  • Authentication and authorization
  • AWS Amplify
  • AWS Cognito
  • The AWS Console
  • react-native-elements

This is a great start, but there’s a lot more we can do now that we have both Redux and Auth in this app. For instance, we can persist our AWS user object throughout our app with Redux. If we look back at our handleSignIn function, we see that AWS returns a user object:

Auth.signIn(email, password)
  // If we are successful, navigate to Home screen
  .then(user => this.props.navigation.navigate('Home'))
  // On failure, display error in console
  .catch(err => console.log(err));

Much like we did with friends in Part 2, we can save this user object in Redux, and then use it to do anything from customizing user welcome messages to bringing in a user’s friend list.

Better than that, now that we have Amplify in our application, we can connect to other AWS services, like a back-end API or Push Notifications.

Thanks to everyone for helping build the next breakthrough in social media. Until next time, good luck and happy coding!

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#