Site icon Experiences Unlimited

Integrating Amazon Cognito With Single Page Application (Vue.js)

Update

I have reviewed this post based on the comments and I have fixed ambiguity in the same. Please refer to the sample code and remember to replace the values in the `.env.local` (https://github.com/sanaulla123/samples/blob/master/aws-cognito-spa-demo/.env.local) file with those which are available in your user pool and app client settings. 

In this article, we will look at authenticating Single page application (built using Vue.js) with Amazon Cognito using OAuth protocol. In our previous article, we integrated a server-side application with Amazon Cognito.

Scaffolding a Single Page Application

We will use vue-cli to create an empty Vuejs application. Vue CLI can be installed by following the instructions here.

Let’s create an empty application called aws-cognito-spa-demo by issuing the following command:

vue create aws-cognito-spa-demo

You will be prompted to choose the plugins

Select the “default” preset

After the application has been created, you can navigate into that directory and issue a command to run the application

cd aws-cognito-spa-demo
npm instal
npm run serve

You will have the application running at http://localhost:8080

Installing Additional Dependencies

We will install the required node packages which we will use for the application:

npm install --save amazon-cognito-auth-js
npm install --save amazon-cognito-identity-js
npm install --save vue-router
npm install --save axios

Creating a User Pool

In Amazon Cognito, the user pool is a directory of users that are used for authentication by your application. These can be users created in the user pool or authenticating via integration with other authentication providers like Facebook, Google, and the likes. 

When you open the Amazon Cognito service page, you will be shown the below:

From here you have to click on “Manage User Pools” to view the user pools you have already created and to create new user pools. You will see the user pools as well as a button to create new user pool as shown below:

Clicking on the “Create a user pool” will take you to the page where you can provide the user pool name and then keep the default settings to set up your new user pool.

Creating New App Client in Amazon Cognito

App clients are created from the user pool detail page you created before. We will create a new App client called test-spa-client from the Amazon Cognito console as shown below:

New app client for SPA integration

Update the settings for the created client by navigating to “App Client Settings” by providing values for Callback URL, Logout URL, Allowed OAuth flow, and OAuth scopes:

We use the Implicit Grant as the OAuth flow for SPA applications.

Creating Environment Variables

We will store the Amazon Cognito related settings in the property files and Vue CLI will make them available among the environment variables during the application’s runtime. More about defining environment variables in Vue JS applications can be found here.

We will store generic application settings like Cognito redirect URI, signout URI in .env the file, and some local settings in .env.local. The .env.*.local and .env.local files are ignored from git. So you don’t commit local settings to version control.

# In .env
VUE_APP_COGNITO_REDIRECT_URI=http://localhost:8080/login/oauth2/code/cognito
VUE_APP_COGNITO_REDIRECT_URI_SIGNOUT=http://localhost:8080/logout
VUE_APP_APP_URL=http://localhost:8080

Then the following in .env.local:

#In .env.local file
VUE_APP_COGNITO_USERPOOL_ID=<cognito userpool id> [This is the name of the user pool. In my case test-spa-client] VUE_APP_COGNITO_APP_DOMAIN=<cognito app domain> [this can be found from the Domain name settings as shown in below image. Enter here without https://] VUE_APP_COGNITO_CLIENT_ID=<app client id>[this can be found from the app client setting page as shown in the image below]
Amazon Cognito Domain Name

 

Amazon Cognito App client ID

Creating User Info Store

We will use a global JSON object for storing the logged-in user information. This is an alternate approach to using Vuex. Let’s create the JSON object in src/app/user-info-store.js:

var state = {
  cognitoInfo: {},
  loggedIn: false,
  loadingState: true,
  errorLoadingState: false
}

function setLoggedIn(newValue) {
  state.loggedIn = newValue;
}

function setLoggedOut() {
  state.loggedIn = false;
  state.cognitoInfo = {};
}

function setCognitoInfo(newValue){
  state.cognitoInfo = newValue;
}

export default {
  state: state,
  setLoggedIn: setLoggedIn,
  setLoggedOut: setLoggedOut,
  setCognitoInfo: setCognitoInfo
}

Wrapper for Amazon Cognito API

Let us create a wrapper src/app/auth.js for Amazon Cognito API which will facilitate operations like building the CognitoAuth object, login, logout:

/* eslint-disable */
import {CognitoAuth, StorageHelper} from 'amazon-cognito-auth-js';
import IndexRouter from '../router/index';
import UserInfoStore from './user-info-store';
import UserInfoApi from './user-info-api';


const CLIENT_ID = process.env.VUE_APP_COGNITO_CLIENT_ID;
const APP_DOMAIN = process.env.VUE_APP_COGNITO_APP_DOMAIN;
const REDIRECT_URI = process.env.VUE_APP_COGNITO_REDIRECT_URI;
const USERPOOL_ID = process.env.VUE_APP_COGNITO_USERPOOL_ID;
const REDIRECT_URI_SIGNOUT = process.env.VUE_APP_COGNITO_REDIRECT_URI_SIGNOUT;
const APP_URL = process.env.VUE_APP_APP_URL;

var authData = {
    ClientId : CLIENT_ID, // Your client id here
    AppWebDomain : APP_DOMAIN,
    TokenScopesArray : ['openid', 'email'],
    RedirectUriSignIn : REDIRECT_URI,
    RedirectUriSignOut : REDIRECT_URI_SIGNOUT,
    UserPoolId : USERPOOL_ID,
}

var auth = new CognitoAuth(authData);
auth.userhandler = {
    onSuccess: function(result) {
        console.log("On Success result", result);
        UserInfoStore.setLoggedIn(true);
        UserInfoApi.getUserInfo().then(response => {
            IndexRouter.push('/');
        });
        
        
    },
    onFailure: function(err) {
        UserInfoStore.setLoggedOut();
        IndexRouter.go({ path: '/error', query: { message: 'Login failed due to ' + err } });
    }
};

function getUserInfoStorageKey(){
    var keyPrefix = 'CognitoIdentityServiceProvider.' + auth.getClientId();
    var tokenUserName = auth.signInUserSession.getAccessToken().getUsername();
    var userInfoKey = keyPrefix + '.' + tokenUserName + '.userInfo';
    return userInfoKey;
}

var storageHelper = new StorageHelper();
var storage = storageHelper.getStorage();
export default{
    auth: auth,
    login(){
        auth.getSession();
    },
    logout(){
        if (auth.isUserSignedIn()) {
            var userInfoKey = this.getUserInfoStorageKey();
            auth.signOut();

            storage.removeItem(userInfoKey);
        }
    },
    getUserInfoStorageKey,

}

Getting User Info From Amazon Cognito

After authentication, we can use the access token to obtain information about the user logged in. For this we will have to make a GET request to the Endpoint: https://<app domain>/oauth2/userInfo. We have created a utility method getUserInfo() in src/app/user-info-api.js as shown below:

import axios from 'axios';
import auth from './auth';


export default{  
    getUserInfo(){
        var jwtToken = auth.auth.getSignInUserSession().getAccessToken().jwtToken;
        const USERINFO_URL = 'https://'+auth.auth.getAppWebDomain() + '/oauth2/userInfo';
        var requestData = {
            headers: {
                'Authorization': 'Bearer '+ jwtToken
            }
        }
        return axios.get(USERINFO_URL, requestData).then(response => { 
            return response.data;
        });
    }
}

This API has been used in the Cognito wrapper written in the section above.

Creating Vue Components

Let’s create some Vue components for:

We will be using Vue Router for mapping the URL path to Vue components. The components definitions are shown below:

Home component

<template>
    <div class="row">
        <div class="col">
            <h3>Welcome, </h3>
            <div class="alert alert-info">
                {{userInfo}}
            </div>
            <router-link to="/logout">
                Logout
            </router-link>
        </div>
    </div>
</template>
<script>
import UserInfoStore from '../app/user-info-store';
export default {
    name: 'Home',
    data: function() {
        return{
            userInfo: UserInfoStore.state.cognitoInfo
        }
    }
}
</script>
<style>
</style>

LogoutSuccess component:

<template>
<div class="row">
    <div class="col">
        <h2>Logged Out successfully</h2>
        <router-link to="/login">Login</router-link>
    </div>
</div>
</template>
<script>
export default {
    mounted: function(){
        
    }
}
</script>

Error component:

<template>
    <div class="alert alert-danger">
        {{message}}
    </div>
</template>
<script>
export default {
    data: function(){
        return {
            message: ""
        }
    },
    mounted(){
        this.message = this.$route.query.message;
    }
}
</script>

Setting Up Router

As mentioned in the previous section, we will be using Vue Router to map URL path to Vue components. We will set up the router configuration in router/index.jsas shown below:

/* eslint-disable */
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import auth from '../app/auth';
import LogoutSuccess from '@/components/LogoutSuccess';
import UserInfoStore from '../app/user-info-store';
import UserInfoApi from '../app/user-info-api';
import ErrorComponent from '@/components/Error';

Vue.use(Router)

function requireAuth(to, from, next) {
  
  if (!auth.auth.isUserSignedIn()) {
      UserInfoStore.setLoggedIn(false);
      next({
      path: '/login',
      query: { redirect: to.fullPath }
      });
  } else {
    UserInfoApi.getUserInfo().then(response => {
      UserInfoStore.setLoggedIn(true);
      UserInfoStore.setCognitoInfo(response);
      next();
    });
      
  }
}

export default new Router({
  mode: 'history',
  base: '/',
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home,
      beforeEnter: requireAuth
    },
    {
      path: '/login', beforeEnter(to, from, next){
        auth.auth.getSession();
      }
    },
    {
      path: '/login/oauth2/code/cognito', beforeEnter(to, from, next){
        var currUrl = window.location.href;
        
        //console.log(currUrl);
        auth.auth.parseCognitoWebResponse(currUrl);
        //next();
      }
    },
    {
      path: '/logout', component: LogoutSuccess,  beforeEnter(to, from, next){
        auth.logout();
        next();
      }

    },
    {
      path: '/error', component: ErrorComponent
    }
  ]
})

We are making use of beforeEnter property of routes object to add any pre-requisites required for rendering the component. And in this property we do the check if the user is logged in or not using the Cognito wrapper we had created. So for paths which require to be protected we can define the beforeEnter property.

The default application created has a App.vue component which will be our root component. We make use of the <router-view/> tag to indicate that the HTML here will be based on the component to which the path gets resolved to in the router configuration

So our version of App.vue looks like:

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <div class="contents">
      <router-view/>
    </div>
  </div>
</template>

<script>
export default {
  name: 'app'
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

We then update the src/main.jsto refer to the directory which contains the router configuration as shown below:

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

Running the Application

You can run the application by issuing the command: npm run serve. Navigating to localhost:8080 will take you to Cognito Login screen:

 

Enter the username and password of the user you had registered in the user pool or you can even signup for a new user. After login you will be redirected back to Vue JS app:

Authenticated User information

The Logout link will log the user out.

The complete code can be found in the Github repo here.

Exit mobile version