Authenticating React Native to AWS Cognito User Pools

I’ve been exploring the world of React Native recently and most recently, I built a login form for React Native, which isn’t too exciting. Today I want to add username and password authentication via Amazon Cognito. Amazon Cognito has a feature called “user pools” that allows me to build a comprehensive username and password sign-up and sign-in flow. In this post, I will:

  • Adjust the Login.js component to do Sign Up as well as Login.
  • Create an Amazon Cognito User Pool.
  • Add logic to sign up and login users.

Switching between Sign Up and Login

In my last post, I built just a login form. I want to make the username an email address, so I’m going to do just that. In the pages/Login.js file, I adjusted the constructor:

    constructor (props) {
        super(props);
        this.state = {
            page: 'Login',
            username: '',
            password: ''
        };
    }

The new thing here is the page property in the state. This will be either Login (the default) or SignUp and will make the component switch between the two modes. I’ve also got a property that returns the alternative mode:

    get alt () { return (this.state.page === 'Login') ? 'SignUp' : 'Login'; }

If the page is ‘Login’, then this returns ‘SignUp’ – and vice versa. I have an event handler that swaps between the two pages:

    togglePage (e) {
        this.setState({ page: this.alt });
        e.preventDefault();
    }

Finally, a mostly unchanged render() method that adds the switcher:

    render() {
        return (
            <ScrollView style={{padding: 20}}>
                <Text style={{fontSize: 27}}>{this.state.page}</Text>
                <TextInput 
                    placeholder='Email Address' 
                    autoCapitalize='none' 
                    autoCorrect={false} 
                    autoFocus={true} 
                    keyboardType='email-address' 
                    value={this.state.username} 
                    onChangeText={(text) => this.setState({ username: text })} />
                <TextInput 
                    placeholder='Password' 
                    autoCapitalize='none' 
                    autoCorrect={false} 
                    secureTextEntry={true} 
                    value={this.state.password} 
                    onChangeText={(text) => this.setState({ password: text })} />
                <View style={{margin: 7}}/>
                <Button onPress={(e) => this.handleClick(e)} title={this.state.page}/>
                <View style={{margin: 7, flexDirection: 'row', justifyContent: 'center'}}>
                    <Text onPress={(e) => this.togglePage(e)} style={{fontSize: 12, color: 'blue'}}>
                        {this.alt}
                    </Text>
                </View>
            </ScrollView>
        );
    }

If you run this code, then you will be able to switch between the SignUp and Login pages by clicking on the appropriate link underneath the button.

Creating an Amazon Cognito User Pool

Before I can go much further, I need a user pool. I already have an AWS account, but if you don’t, you will need to sign up for Amazon Web Services, put in a credit card, confirm your details, etc. Once done, open up the AWS Console. The following is a walk-through of the steps I took to create the user pool.

  1. Click Services in the top left corner, then type in Cognito into the search box and hit Enter. There are other ways of getting to the Cognito console, but this is perhaps the easiest.
  2. Click Manage your User Pools.
  3. Click Create a User Pool in the top right corner.
  4. Enter a nice pool name (I used ‘blog-code’), then click Review defaults.
  5. Edit any section you wish by clicking on the ‘pencil’ icon next to them. For this demo, I changed the password settings to not require a special character.
  6. Click App Clients in the left hand menu, then click Add an app client.
  7. Give the app client a nice name. De-check the Generate client secret box, then click Create app client.
  8. Click Return to pool details.
  9. Click Create Pool.

This produces the following screen:

Note the highlighted “Pool Id” – I will need this in the next section. Click on App Clients again:

I can come back to these pages and copy-paste the values into your code, so don’t worry about writing them down. I’ve now got a working Amazon Cognito User Pool, just waiting to be used.

Integrate Amazon Cognito into React Native

Let’s start by integrating the very latest react-native compatible AWS SDK into the project:

npm install --save git://github.com/aws/aws-sdk-js.git#react-native

This is obviously pre-release code here. Once they release a React Native version of the library, you can just use the normal AWS SDK. I also need the amazon-cognito-identity-js library. However, there is a problem here. The library does not work with React Native. Fortunately, there isn’t much change needed to make it React Native compatible. Create a directory in the project called lib/aws-cognito-identity and copy the files in the libraries src directory into the newly created directory. The following files need to be adjusted:

  • AuthenticationHelper.js
  • CognitoAccessToken.js
  • CognitoIdToken.js
  • CognitoUser.js

Each of these files has a line near the top:

import { util } from 'aws-sdk/global';

This needs to be changed to the following:

import { util } from 'aws-sdk/dist/aws-sdk-react-native';

In addition, CognitoUserPool.js needs an adjustment. The import of CognitoIdentityServiceProvider needs to be adjusted:

import { CognitoIdentityServiceProvider } from 'aws-sdk/dist/aws-sdk-react-native';

These changes pull in the React Native compatible version of the library rather than the Node.js compatible version of the library. If you see references to missing modules like ‘events’ when building, it’s a pretty good sign that you are using the Node.js compatible version of the library.

Now that I have a compatible version of the libraries, I can move on to integrating it into my pages/Login.js page. Start by adding the library and configuring the user pool:

import { 
    AuthenticationDetails, 
    CognitoUser, 
    CognitoUserAttribute, 
    CognitoUserPool 
} from '../lib/aws-cognito-identity';

const awsCognitoSettings = {
    UserPoolId: 'us-west-2_hUQZ8Zqk7',
    ClientId: '4ult2iqtq58sjsgf058r7jmng0'
};

The UserPoolId and ClientId values come from the Pool details and App Clients pages in the Cognito console. Just copy-paste them in. The new handleClick() method in the Login class looks like this:

    handleClick (e) {
        e.preventDefault();
        const userPool = new CognitoUserPool(awsCognitoSettings);

        // Sign up
        if (this.state.page === 'SignUp') {
            const attributeList = [
                new CognitoUserAttribute({ Name: 'email', Value: this.state.userame })
            ];
            userPool.signUp(
                this.state.username,
                this.state.password,
                attributeList,
                null,
                (err, result) => {
                    if (err) {
                        alert(err);
                        this.setState({ username: '', password: '' });
                        return;
                    }
                    console.log(`result = ${JSON.stringify(result)}`);
                    alert('Sign Up Successful.  Check your Email for a verification');
                    this.setState({ page: 'Login' });
                }
            );
        } else {
            const authDetails = new AuthenticationDetails({
                Username: this.state.username,
                Password: this.state.password
            });
            const cognitoUser = new CognitoUser({
                Username: this.state.username,
                Pool: userPool
            });
            cognitoUser.authenticateUser(authDetails, {
                onSuccess: (result) => {
                    console.log(`access token = ${result.getAccessToken().getJwtToken()}`);
                    this.props.onLogin(this.state.username, this.state.password);
                },
                onFailure: (err) => {
                    alert(err);
                    this.setState({ username: '', password: '' });
                    return;
                }
            });
        }
    }

The first block handles the sign-up process. The second block handles the authentication process. These are snippets based primarily on the examples in the library README file. The only thing I do after the sign-up is set the page back to the login page with the same username and password, and an alert telling the user to check their email.

The second block handles the authentication. I should be storing the access token within the Redux store, but that is a simple change later on.

Testing

To test, use the normal react-native run-android command. Firstly, go through the sign-up process. Once you click on SignUp and get the alert, switch over to the AWS Console. Eventually (and it does take a fair number of minutes!) you will be able to refresh the pool details and see that a user has been created. At that point, you can click on Users in the side-bar, find the user, click on the user, and then click Confirm User. When I was testing this, I did not get the email, which means it is likely that the email was rejected for spam along the way. I need to look into this, but I’m happy that the administrator can confirm a user as well.

Once you have confirmed the user, switch back over to your app and click Login. The username and password should still be available. You will be authenticated, and your access token will be displayed in the debug window:

If you copy-paste that token into the token validator at jwt.io, you will see that it is a standard JWT and contains the username, pool Id and client Id for your pool.

Wrap Up

This is not the end of this process. Merely authenticating with Cognito does not get that token any access to other Amazon resources, for example. However, I hope this gives a good run-down of the basic flow of sign-up and login.

The code, as always, is available on my GitHub Repository.

One thought

  1. Pingback: Dew Drop - May 16, 2017 (#2480) - Morning Dew

Comments are closed.