User management in Vue.js with AWS Cognito
Using AWS Cognito, Amplify and aws-amplify-vue components to facilitate user management and authentication in a Vue.js web app
This is the sixth of a series of posts describing my experimentation with Vue.js, AWS AppSync, Lambda and other AWS technologies. A public GitHub repository exists with this experiment so others can learn from my efforts. For this post, I will be describing the code that resides on the v4 tag, so feel free to checkout to this point in time:
git checkout v4. Links to the other posts in this series can be found in the first post.
In the first three posts for this Invest Guru endeavor, I built up a simple GraphQL API that uses AWS AppSync to host a GraphQL API and AWS Lambda and the Python 3.7 runtime to implement a simple resolver for handling a simple moving average query. I use the excellent Alpha Vantage API to source the simple moving average data for a specific company. I have demonstrated that the GraphQL query works by exercising it from Prisma’s GraphQL Playground.
In the fourth and fifth posts, I started working on the front-end, building out a Vue.js web application that will consume the GraphQL API and present the data to the user in various presentations. I then continued to build out this Vue.js-based web application, enlisting the help of Chart.js and vue-chartjs to help me plot the simple moving average data that I am consuming from the back-end.
In this post, I will be incorporating the AWS Cognito managed service to provide user management services on the backend. In the Vue.js web application, I will use AWS Amplify and the aws-amplify-vue module to provide basic user sign up, sign in, and sign out functionality. I want to give a shout out to Nader Dabit for his blog post and source code that inspired a good portion of the Vue.js work described in this blog post.
I will start on the AWS side of things and then progress to the Vue.js side.
AWS Cognito CloudFormation configuration
I updated the serverless.yml file, adding configuration for the creation of Cognito User Pools. The entire contents of the serverless.yml file are shown below:
- Starting at line 66, I’ve added an IAM ManagedPolicy for allowing Cognito to access SNS.
- Starting at line 80, I’ve added an IAM Role that is associated with the aformentioned IAM ManagedPolicy, allowing Cognito to send messages to SNS.
- Starting at line 105, I create the Cognito User Pool. Most of this should be self-explanatory. I’ve turned on multi-factor authentication (MFA), using SMS to send MFA confirmation codes to users’ cell phones.
- Starting at line 126, I create a Cognito UserPoolClient which will allow AppSync to access and use the Cognito User Pool.
- The AppSyncApi configuration has changed to support Cognito User Pools. The changes start at line 155. UserPoolConfig is new and is used to configure the User Pool integration with AppSync.
- Outputs, starting at line 209, have changed. I removed the API key and added outputs for UserPoolId and UserPoolClientId. Those two values will be needed on the front-end side.
Update Vue.js environment variables
You should have either a
invest-guru-prototype1-vue/.env file or a
invest-guru-prototype1-vue/.env.local file for the Vue.js project — you may have both. Update the
.env.local file if you have it or create the
.env.local file and copy pasta the following into the file:
Obviously the above values for
VUE_APP_USER_POOL_CLIENT_ID are not correct for your environment, so replace these values with the values output during the
serverless deployment to AWS. Adjust the
VUE_APP_AWS_APPSYNC_REGION if these values are different in your AWS named profile.
Install the aws-amplify and aws-amplify-vue dependencies in the Vue.js project
yarn add aws-amplify aws-amplify-vue#ornpm i --save aws-amplify aws-amplify-vue
Create the aws-exports.js file
src/aws-exports.js file in the Vue project. Use the following content for the contents of this file:
You will notice that this file pulls the configuration values from the Node.js process environment variables. This allows me to version control this file in git and keep my secret values maintained in the appropriate
.env.* files for each mode/stage/environment. The modes and environment variables feature in Vue CLI provides this functionality out of the box and I highly encourage you to use it here.
Update the appsync.js to use a JWT
Previously we were using an API key that we generated on the AWS side of the house to guard the AppSync-host GraphQL API endpoint. That will be changing now, since we will be authenticating the user via Cognito, we will have access to the current session’s access token and thus a JWT. Lines 10–13 for the listing below provides access to the JWT for the current session.
Vue.prototype.$Amplify allows global access to the Vue Amplify plugin. I am just accessing it through the
Vue prototype. If I was working in a Vue component, I could have used
this.$Amplify instead. We are using a callback function here so that we can always access the current JWT from anywhere and ensure that you always have the most up-to-date authentication token available through the Amplify module. I happened upon this trick via Adrian Hall’s blog post. The
await just makes it easier to read;
Auth.currentSession() returns a Promise, so we have to deal with that little nuance.
Update main.js to use AWS Amplify
AWS Amplify is configured and loaded in
- First we configure Amplify with our
awsmobileconfiguration that you saw earlier (line 11).
- Next, on line 16, we load the
AmplifyModulesplugins using the standard
Vue.use()global method. Ensure this happens before starting the app at line 18.
Update router.js to guard auth-required routes
The router.js file undergoes some major changes.
- I assigned the instantiation of the
Routerto a const
routerso that I can call the
beforeResolvefunction on it after it’s creation. You can read more about Vue Router Global Resolve Guards here.
- In lines 16 and 22, I am adding metadata to the routes for the requires authentication constraint. You can read about route metadata here.
- Lines 27–42 provides the functionality for checking for the currently authenticated user via Amplify Auth. If an authenticated user exists, it will be returned in a Promise (which we don’t do anything with here, so it doesn’t get assigned to a variable). If an authenticated user does not exist, the function call with throw an exception. Thus the code from lines 33–38 handles capturing the original path that we were trying to navigate to and redirects to the root path, which happens to be the
FrontDoorcomponent that contains our authentication UI.
- Again, since we’re using an
awaitin anonymous function, it has to be labeled as an
asyncfunction (in line 27).
Create the Header.vue component
I need a header component so I can render a sign out link/button. This will reside in the
- I am using standard Bootstrap 4 classes here for the nav bar.
- On line 19, I am using a AWS Amplify Vue component for sign out functionality. Very handy for getting sign out functionality up and running, but I’m not sure it will survive long in the application. I found it difficult to deviate the styling from the default Amazon styling that they have baked into the Vue components. But it does get me up and running with the functionality. It survives for today!
- On line 18, I’m toggling the rendering of the
lielement and its descendants via the
v-if="signedIn"attribute. More about the
signedIndata property in a moment.
signedIndata property management is being handled in the
beforeCreatelifecycle function, listed on lines 36–46. This will set the initial value for
signedInand then we register an event listener on the
authStatechanges. You can read more about the AmplifyEventBus here. I only care about the
signedInevent, so I test to see if
inforepresents this event (
infois a String type). This event handler allows us to change the
signedIndata property after the component has been created, mounted and rendered.
Render the header in App.vue
Now that I possess a Header component, I will render it!
- Standard Vue stuff here. Reference the component in your
<script>section of the component file (line 9 for the import, line 12 for the component registration). Use the component in the
<template>section (line 3).
Add the amplify-authenticator to the FrontDoor.vue component
I have the sign out covered now, but I still need to be able to handle sign up/register, sign in, and multi-factor confirmation. The aws-amplify-vue has a single component for that:
<amplify-authenticator>! The AWS Amplify style guide has some good information on colors and typography and screenshots of the components provided in the aws-amplify-vue module.
- On line 3, add the
<amplify-authenticator>to the template. This Vue component will handle sign up/register, sign in, and MFA confirmation, so it’s quite a powerful little component!
- On lines 15–23, we use a
mountedlifecycle function to register another
authStateevent listener on the
AmplifyEventBusabstraction. Here I am responding to authState changes, routing the application to different routes when different authStates are encountered. This is how we navigate to the Home component after signing in and to the FrontDoor component when we sign out. I’m using named routes here just demonstrate their use.
A couple of thoughts that come to mind as I conclude the work for this blog post:
- Never under-estimate the power of user management services like AWS Cognito. I would be very skeptical if you’re writing your own user management services in the cloud these days. Getting Cognito up and running and integrated with AWS AppSync with some CloudFormation configuration was very easy to accomplish. Kudos to AWS for really promoting managed and hosted services as a viable way of building applications.
- I’m finding CloudFormation YAML very easy to use, though I’m not sure if I would continue to use it if I were building out multiple services with the Serverless Framework. This may be something better handled by provisioning AWS resources with Terraform and exchanging resource values through AWS Systems Manager Parameter Store to the Serverless configuration for the Lambda deployment. I may play with this at some point and will definitely write something up if anything fruitful comes out of that experimentation.
- I was blown away by the power of the AWS Amplify library and the Vue components. It took very little time to get basic sign up/register, sign in, and MFA confirmation working in this Invest Guru app. The jury is still out on whether I’ll continue to use the Vue components due to my initial failures to correctly style the components, but I attribute that more to my unfamiliarity with the components than anything. Tools like AWS Amplify and Google’s Firebase SDKs make it so easy to build and integrate their respective cloud services into your web and mobile applications. Yes, there is cloud lock-in when doing this, so you do need to keep that in mind when going down this route.