Part 5 - Getting Started With AWS Cognito Image Part 5 - Getting Started With AWS Cognito

Part 5 - Getting Started With AWS Cognito

Elliot Forbes ⏰ 9 Minutes 📅 Apr 20, 2018

In the previous tutorial, we managed to get our DynamoDB table set up and populated with a couple of very simple posts. In this tutorial, we are going to set up a Cognito pool that will store all of the users that register for our Blog. We’ll also be able to improve our Lambda endpoints so that they aren’t open to the world, they require authentication before they start updating our database, this will stop unauthorized people potentially updating posts and doing malicious things.

Step 1 - Setting up a Pool

So, the first thing we’ll need to do is to provision a new User Pool within the Cognito service. We can do this by navigating to the Cognito service page and clicking “Manage Pools”. Once we’ve done that, you should see a page that looks quite similar to this:

You’ll notice the blue Create a user pool button in the top right hand corner, click on it and we’ll walk through the options.

First, we’ll specify the name, in this case I’ve gone for a reasonably apt name: vuejs-blog-aws:

Next, we’ll want to click on the Review defaults button below where it asks how we wish to create our pool.

The standard settings are perfect for what we are trying to achieve, so lets leave everything as it stands and click create at the bottom of the page.

We should then see our console go off and provision the pool for us. If everything went to plan then you should see a screen similar to the one below, it’ll contain our Pool ID and it’s ARN.

Finally, we’ll want to add an App Client to our user pool so that we can start to interact with the cognito service from our frontend blog:

Once, we’ve done this, we should be good to move onto the next part of our project and create authenticated Routes and a sign-in.

Step 2 - Authentication with Cognito

So, we’ve got our User Pool all sorted, we also have our App’s client ID. We’ll also be utilizing the amazon-cognito-identity-js and aws-sdk node modules in order to communicate with our AWS Cognito service.

We can install these like so:

$ npm install amazon-cognito-identity-js aws-sdk --save

We’ll then need to create a config file within our frontend project that will feature the following:

  • Region - The region in which our AWS User Pool resides
  • IdentityPoolId - The Pool Id supplied within the General settings tab for our Cognito Pool
  • UserPoolId - This looks very similar to this: eu-west-1:853957954650 which can be found within the generated Pool ARN.
  • ClientId - The App client ID

This will be put into a file that looks like this:

// src/config/index.js
export default {
  region: "eu-west-1",
  IdentityPoolId: "eu-west-1_9IBAarCx9",
  UserPoolId: "eu-west-1:853957954650",
  ClientId: "43duengi4ldb6jel18p84sgq22"
};

I’ll admit that I was somewhat apprehensive about putting these values into my frontend source code, in which people can inspect it when it goes live into production. However, these are only used to hit unathenticated endpoints and as such, hackers cannot use them to do anything malicious.

Next, let’s create a new cognito service within our application that will handle all of our cognito-based interaction:

// src/cognito/cognito.js
import { Config, CognitoIdentityCredentials } from "aws-sdk";
import {
  CognitoUser,
  CognitoUserPool,
  AuthenticationDetails,
  CognitoUserAttribute
} from "amazon-cognito-identity-js";

export default class CognitoAuth {
  constructor() {
    this.userSession = null;
  }
}

Some Simple Functions

Let’s now start to populate our CognitoAuth class with a couple of functions. We’ll start by creating an isAuthenticated() method which will verify if a user is authenticated against the Cognito service, and a configure() method which we may need further down the line:

isAuthenticated (cb) {
    let cognitoUser = this.getCurrentUser()
    if (cognitoUser != null) {
        cognitoUser.getSession((err, session) => {
        if (err) {
            return cb(err, false)
        }
        return cb(null, true)
        })
    } else {
        cb(null, false)
    }
}

configure (config) {
    if (typeof config !== 'object' || Array.isArray(config)) {
        throw new Error('[CognitoAuth error] valid option object required')
    }
    this.userPool = new CognitoUserPool({
        UserPoolId: config.IdentityPoolId,
        ClientId: config.ClientId
    })
    Config.region = config.region
    Config.credentials = new CognitoIdentityCredentials({
        IdentityPoolId: config.IdentityPoolId
    })
    this.options = config
}

Next, we’ll need need a signup method which will, funnily enough, allow people to sign up to our blog and subsequently be able to post their own blog posts up to the site.

signup(username, email, pass, cb) {
    let attributeList = [
        new CognitoUserAttribute({
            Name: 'email',
            Value: email
        })
    ]

    this.userPool.signUp(username, pass, attributeList, null, cb)
}

We will also need an authenticate method which will validate whether someone who is attempting to login has valid credentials.

authenticate (username, pass, cb) {

    let authenticationData = { Username: username, Password: pass }
    let authenticationDetails = new AuthenticationDetails(authenticationData)
    let userData = { Username: username, Pool: this.userPool }
    let cognitoUser = new CognitoUser(userData)

    cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function (result) {
            console.log('access token + ' + result.getAccessToken().getJwtToken())
            var logins = {}
            logins['cognito-idp.' + this.options.region + '.amazonaws.com/' + this.options.UserPoolId] = result.getIdToken().getJwtToken()
            console.log(logins)


            Config.credentials = new CognitoIdentityCredentials({
                IdentityPoolId: this.options.UserPoolId,
                Logins: logins
            })
            console.log(Config.credentials)
            this.onChange(true)
            cb(null, result)
        },
        onFailure: function (err) {
            cb(err);
        },
        newPasswordRequired: function (userAttributes, requiredAttributes) {
            console.log('New Password Is Required')
        }
    })
}

Let’s now have a look at our next batch of functions. We’ll want to be able to retrieve a logged-in user’s details, we’ll also want a method that will allow us to confirm a user’s registration. We’ll also want a logout method and a getIdToken() method so, let’s dive into the code:

getCurrentUser () {
    return this.userPool.getCurrentUser()
}

confirmRegistration (username, code, cb) {
    let cognitoUser = new CognitoUser({
        Username: username,
        Pool: this.userPool
    })
    cognitoUser.confirmRegistration(code, true, cb)
}

/**
  * Logout of your cognito session.
  */
logout () {
    this.getCurrentUser().signOut()
    this.onChange(false)
}

/**
* Resolves the current token based on a user session. If there
* is no session it returns null.
* @param {*} cb callback
*/
getIdToken (cb) {
    if (this.getCurrentUser() == null) {
        return cb(null, null)
    }
    this.getCurrentUser().getSession((err, session) => {
        if (err) return cb(err)
        if (session.isValid()) {
        return cb(null, session.getIdToken().getJwtToken())
        }
        cb(Error('Session is invalid'))
    })
}

Finally, we’ll want to create an index.js within our cognito directory which will export our new CognitoAuth class like so:

// src/cognito/index.js
import Vue from "vue";
import CognitoAuth from "./cognito";
import config from "@/config";

Vue.use(CognitoAuth, config);

export default new CognitoAuth();

Our Register Component

<template>
  <div class="login-box">
    <h4>Register</h4>
    <div class="card-panel red darken-2" v-if="error != null">
      <span class="white-text">{{ error.message }}</span>
    </div>
    <p>Don't have an account? Register for one using your Google account</p>
    <form @submit.prevent="authenticate">
      <div class="input-field">
        <input
          id="username"
          type="text"
          class="validate"
          v-model="username"
          required
        />
        <label for="username">Username</label>
      </div>
      <div class="input-field">
        <input
          id="email"
          type="text"
          class="validate"
          v-model="email"
          required
        />
        <label for="email">Email</label>
      </div>
      <div class="input-field">
        <input
          id="password"
          type="password"
          class="validate"
          v-model="pass"
          required
        />
        <label for="password">Password</label>
      </div>
      <div class="center-align">
        <button class="btn btn-default btn-large">Register</button>
      </div>
    </form>
  </div>
</template>

<script>
  export default {
    name: "Login",
    data() {
      return {
        username: "",
        pass: "",
        email: "",
        error: null
      };
    },
    methods: {
      authenticate() {
        this.$cognitoAuth.signup(
          this.username,
          this.email,
          this.pass,
          (err, result) => {
            if (err) {
              this.error = err;
            } else {
              this.$router.replace({ path: "/confirm" });
            }
          }
        );
      }
    }
  };
</script>

<style scoped>
  h4 {
    text-align: center;
    margin: 0;
    padding: 0;
    font-weight: 800;
    font-size: 18px;
  }
  p {
    text-align: center;
    font-size: 14px;
    padding-bottom: 10px;
  }
  .login-box {
    width: 400px;
    height: auto;
    background-color: white;
    margin-top: 60px;
    border-radius: 5px;
    padding: 40px;
    margin: auto;
    border: 1px solid #e4e6e7;
    box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.4);
  }
  button {
    margin: auto;
    background-color: #fa3254;
    margin: 0;
    padding: 0px 40px;
  }

  button i {
    font-size: 18px;
  }
</style>

Our Confirm Component

<template>
  <div class="login-box center-align">
    <h4>Confirm Signup</h4>
    <div class="card-panel red darken-2" v-if="error != null">
      <span class="white-text">{{ error.message }}</span>
    </div>
    <p>Enter the verification code you should have recieved via email</p>
    <form @submit.prevent="login">
      <div class="input-field">
        <input
          id="username"
          type="text"
          class="validate"
          v-model="username"
          required
        />
        <label for="username">Username</label>
      </div>
      <div class="input-field">
        <input
          id="confirmcode"
          type="text"
          class="validate"
          v-model="confirmcode"
          required
        />
        <label for="confirmcode">Confirmation Code</label>
      </div>
      <div class="center-align">
        <button class="btn btn-default btn-large">Verify Now</button>
      </div>
    </form>
  </div>
</template>

<script>
  export default {
    name: "Confirm",
    data() {
      return {
        username: "",
        confirmcode: "",
        error: null,
        loading: false
      };
    },
    methods: {
      login() {
        this.loading = true;
        this.$cognitoAuth.confirmRegistration(
          this.username,
          this.confirmcode,
          (err, result) => {
            if (err.statusCode !== 200) {
              this.error = err;
            } else {
              console.log("Successfully Verified");
              this.$router.replace("/profile");
            }
          }
        );
      }
    }
  };
  //confirmcode
</script>

<style scoped>
  h4 {
    text-align: center;
    margin: 0;
    padding: 0;
    font-weight: 800;
    font-size: 18px;
  }
  p {
    text-align: center;
    font-size: 14px;
    padding-bottom: 10px;
  }
  .login-box {
    width: 400px;
    height: auto;
    background-color: white;
    margin-top: 60px;
    border-radius: 5px;
    padding: 40px;
    margin: auto;
    border: 1px solid #e4e6e7;
    box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.4);
  }
  button {
    margin: auto;
    background-color: #fa3254;
    margin: 0;
    padding: 0px 40px;
  }

  button i {
    font-size: 18px;
  }
</style>

Our Login Component

So, now that we’ve created our User Pool and built our service, let’s now create a component within our VueJS application that will allow us to log in or register should we not already have an account.

Let’s build our Login component first. Create a new file called Login.vue within your src/component/ directory. Within this, we’ll be defining our three standard sections, the <template> section, the <script> section, and the <style> section.

Within our <template> section, let’s add the following code:

<template>
  <div class="login-box">
    <h4>LOGIN</h4>
    <div class="card-panel red darken-2" v-if="error != null">
      <span class="white-text">{{ error.message }}</span>
    </div>
    <p>Login to upload your own images to the site!</p>
    <form @submit.prevent="login">
      <div class="input-field">
        <input
          id="username"
          type="text"
          class="validate"
          v-model="username"
          required
        />
        <label for="username">Username</label>
      </div>
      <div class="input-field">
        <input
          id="password"
          type="password"
          class="validate"
          v-model="pass"
          required
        />
        <label for="password">Password</label>
      </div>
      <div class="center-align">
        <button class="btn btn-default btn-large">login</button>
        <br />
        <p>
          Don't have an account? -
          <router-link to="Register">Register Now</router-link>
        </p>
      </div>
    </form>
  </div>
</template>

This will take in a username and a password and will hit the login() method we are going to define below.

Now that’s done, let’s add our JavaScript, this will feature just a simple login() method that will take any inputted credentials and attempt to authenticate against our Cognito service using them. When the authenticate method returns, we’ll be able to check to see if it was successful, upon which we’ll redirect to the /profile page, or unsuccessful, upon which we’ll log out our issue.

export default {
  name: "Login",
  data() {
    return {
      username: "",
      pass: "",
      error: null,
      loading: false
    };
  },
  methods: {
    login() {
      this.loading = true;
      this.$cognitoAuth.authenticate(
        this.username,
        this.pass,
        (err, result) => {
          if (err.statusCode !== 200) {
            console.log(err);
            this.error = err;
          } else {
            this.$router.replace("/profile");
          }
        }
      );
    }
  }
};

And finally, some really simple css rules that will make our Login form look a little nicer.

h4 {
  text-align: center;
  margin: 0;
  padding: 0;
  font-weight: 800;
  font-size: 18px;
}
p {
  text-align: center;
  font-size: 14px;
  padding-bottom: 10px;
}
.login-box {
  width: 400px;
  height: auto;
  background-color: white;
  margin-top: 60px;
  border-radius: 5px;
  padding: 40px;
  margin: auto;
  border: 1px solid #e4e6e7;
  box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.4);
}
button {
  margin: auto;
  background-color: #fa3254;
  margin: 0;
  padding: 0px 40px;
}

button i {
  font-size: 18px;
}

Conclusion

In this part of the course, we managed to successfully extend our project so that it interacts nicely with the AWS Cognito service. We have implemented full login/register functionality and consequently we can now start to build more of a community around our topic of choice.

The next parts of this course are currently under construction.