Enterprise Java

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

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:

1
vue create aws-cognito-spa-demo

You will be prompted to choose the plugins

Amazon Cognito

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

1
2
3
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:

1
2
3
4
npm install --save amazon-cognito-auth-js
npm install --save amazon-cognito-identity-js
npm install --save vue-router
npm install --save axios

Creating New App Client in Amazon Cognito

We will create a new App client called test-spa-client from the Amazon Cognito console as shown below:

Amazon Cognito

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:

Amazon Cognito

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 file and some local settings in .env.local. The .env.*.local and .env.local files are ignored from git. So you dont commit local settings to version control.

1
2
3
4
# 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:

1
2
3
VUE_APP_COGNITO_USERPOOL_ID=<cognito userpool id>
VUE_APP_COGNITO_APP_DOMAIN=<cognito app domain>
VUE_APP_COGNITO_CLIENT_ID=<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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/* 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 End point: https://<app domain>/oauth2/userInfo. We have created an utility method getUserInfo() in src/app/user-info.js as shown below:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
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 section above.

Creating Vue Components

Lets create some Vue components for:

  • showing the logged in user information
  • showing log out success
  • error handling component

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

Home component

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<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 previous section, we will be using Vue Router to map URL path to Vue components. We will setup the router configuration in router/index.jsas shown below:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* 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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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:

01
02
03
04
05
06
07
08
09
10
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:

Amazon Cognito

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:

Amazon Cognito

The Logout link will log the user out.

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

Published on Java Code Geeks with permission by Mohamed Sanaulla, partner at our JCG program. See the original article here: Integrating Amazon Cognito With Single Page Application (Vue.js)

Opinions expressed by Java Code Geeks contributors are their own.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
mbk
mbk
4 years ago

For your tutorial to work, does it require ‘Implicit’ grant? I have a Cognito User Pool set up for Authorization Code and it hangs. It’s trying to redirect I think but it’s not configured correctly. Wondering what changes I would need to make to your code to allow it to support Authorization Code grant? Thanks!

Back to top button