Authentication in a React Native App Using AWS Amplify – HAPITECHS

Authentication in a React Native App Using AWS Amplify

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:

Authentication in a React Native App Using AWS Amplify


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 in a React Native App Using AWS Amplify


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 in a React Native App Using AWS Amplify


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:

Authentication in a React Native App Using AWS Amplify


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.

Authentication in a React Native App Using AWS Amplify


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.

Authentication in a React Native App Using AWS Amplify


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

Authentication in a React Native App Using AWS Amplify


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

Authentication in a React Native App Using AWS Amplify


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

Authentication in a React Native App Using AWS Amplify


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:

Authentication in a React Native App Using AWS Amplify


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:

Authentication in a React Native App Using AWS Amplify


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:

Authentication in a React Native App Using AWS Amplify


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:

Authentication in a React Native App Using AWS Amplify


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!

Leave a Reply

Your email address will not be published. Required fields are marked *