Using AWS Cognito with Vue/NuxtJS

Using AWS Cognito with Vue/NuxtJS

In my last post, "Using AWS Cognito with Xamarin Forms", I showed how to authenticate with Amazon Cognito using Xamarin Forms and the Xamarin.Essentials Web Authenticator.

In this post, we'll go through the process of using the AWS Cognito Hosted UI with your own Vue/NuxtJS site. For an idea on the final result, please take a look at the YouTube Video

Setting up an AWS Cognito user pool

We'll be using the same AWS Cognito User pool and App client that we've created in my previous post. Please refer to the instructions details therein.

Creating the Vue/Nuxt project

To create the Nuxt project, open up your preferred terminal window and enter the following command:

yarn create nuxt-app mynuxtapp

When prompted, you can select the following options:

  1. Project name: mynuxtapp
  2. Programming language: JavaScript
  3. Package manager: Yarn
  4. UI framework: Vuetify.js
  5. Nuxt.js modules: Axios
  6. Rendering mode: Single Page App
  7. Deployment target: Static (Static/Jamstack hosting)

I've assumed you've already installed everything necessary to create Vue/Nuxt projects. If not, please refer to the NuxtJS documentation

Adding the nuxt/auth dependency

Add the nuxt/auth dependency to you project by executing the following command:

yarn add --exact @nuxtjs/auth-next

Next, in the nuxt.config.js file, add the nuxt/auth module to the modules array.

  modules: [
    "@nuxtjs/axios",
    "@nuxtjs/auth-next"
  ],

While we have the nuxt.config.js file open, also add the auth section as below:

  auth: {
    strategies: {
      awsCognito: {
        scheme: "oauth2",
        endpoints: {
          authorization: "https://myxamarinapp.auth.us-east-1.amazoncognito.com/login",
          token: "https://myxamarinapp.auth.us-east-1.amazoncognito.com/oauth2/token",
          userInfo: "https://myxamarinapp.auth.us-east-1.amazoncognito.com/oauth2/userInfo",
          logout: "https://myxamarinapp.auth.us-east-1.amazoncognito.com/logout"
        },
        token: {
          property: "access_token",
          type: "Bearer",
          maxAge: 3600
        },
        refreshToken: {
          property: "refresh_token",
          maxAge: 60 * 60 * 24 * 30
        },
        responseType: "token",
        redirectUri: "http://localhost:3000/login",
        logoutRedirectUri: "http://localhost:3000/login",
        clientId: "4jlfe2iki0ucn32uc44clmib3d",
        scope: ["email", "openid", "profile"],
        codeChallengeMethod: "S256"
      }
    }
  }

We're using the built-in OAuth2 scheme and we're calling it awsCognito. The endpoints are:

authorization This is the domain/url we've configured in AWS Cognito with /login appended. This loads the login page.

token This is the domain/url we've configured in AWS Cognito with /oauth2/token appended. This endpoint is used to get the user's tokens.

userInfo This is the domain/url we've configured in AWS Cognito with /oauth2/userInfo appended. This endpoint is used to retrieve information about the authenticated user.

logout This is the domain/url we've configured in AWS Cognito with /logout appended. Used to sign the user out.

For more detail regarding these endpoints, please refer to the Amazon Cognito user pools Auth API Reference

The rest of the auth options should be relatively self-explanatory. For this example the important config entries are:

response_type Set this to token to inform AWS Cognito that we want an access_token back.

redirectUri This value should also be added to the list of Callback URL(s) under App integration > App client settings in AWS Cognito. image

clientId Is the same Client Id as configured in AWS Cognito. You'll find this under General settings > App clients image

codeChallengeMethod Amazon Cognito authentication server only support S256. This value must be supplied for the nuxt/auth OAuth scheme to work or else you'll receive a "unsupported code challenge method" error.

The last change we need to make in the nuxt.config.js file is to enable the auth middleware globally. Do this by setting the router middleware, as show below:

 router: {
    middleware: ["auth"]
  }

UI Changes

Login Page We'll add a simple login page, inside the pages folder, called login.vue. The page will prompt the user to log in if they are not authenticated. I contains a simple login method that logs in using the awsCognito scheme we've configured in the previous step, the code for the method follows:

methods: {
    login() {
      this.$auth.loginWith("awsCognito");
    }
  }

The full code listing for the login page are:

<template>
  <v-row align="center" justify="center">
    <v-col cols="12" sm="8" md="4">
      <v-card class="elevation-12" v-if="loggedIn">
        <v-card-title>Logging in.</v-card-title>
        <v-card-text
          >Logging in, please wait...
          <v-progress-circular indeterminate color="green"></v-progress-circular>
        </v-card-text>
      </v-card>
      <v-card class="elevation-12" v-else>
        <v-card-title>You're not logged in</v-card-title>
        <v-card-text>Click the button to log into your account. </v-card-text>
        <v-card-actions>
          <v-btn @click="login" color="primary">Login</v-btn>
        </v-card-actions>
      </v-card>
    </v-col>
  </v-row>
</template>

<script>
export default {
  layout: "unauthorized",
  data() {
    return {
      loggedIn: false
    };
  },
  created() {
    this.loggedIn = this.$auth.strategy.token.get();
  },
  methods: {
    login() {
      this.$auth.loginWith("awsCognito");
    }
  }
};
</script>

In the page template we use v-if and v-else directives to either show a login button or a message informing the user that they are being logged in. This is useful when AWS Cognito redirects to the login page after the user has logged in.

Home Page The home page is located in the pages/index.vue file. This file should've been automatically added for you when you've created the project. This page will only be shown once the user is authenticated and will display some of the user's information as illustrated in the following image: image

There is not much logic in the page. The most important aspects is that we show the user information using properties on the this.$auth.user object and the logOut method uses this.$auth.logout() to log the user out when the "Log Out" button is clicked.

The full code listing for the page follows:

<template>
  <v-row justify="center" align="center">
    <v-col cols="12" sm="8" md="6">
      <div class="text-center">
        <logo />
        <vuetify-logo />
      </div>
      <v-card>
        <v-card-title class="headline"> Welcome {{ username }}, you are logged in! </v-card-title>
        <v-card-text>
          <p>
            Here are your user details, retrieved from the
            <a href="https://docs.aws.amazon.com/cognito/latest/developerguide/userinfo-endpoint.html">/USERINFO</a>
            endpoint:
          </p>
          <template>
            <v-simple-table>
              <template v-slot:default>
                <thead>
                  <tr>
                    <th class="text-left">
                      Property
                    </th>
                    <th class="text-left">
                      Value
                    </th>
                  </tr>
                </thead>
                <tbody>
                  <tr>
                    <td>Email</td>
                    <td>{{ email }}</td>
                  </tr>
                  <tr>
                    <td>Phone number</td>
                    <td>{{ phoneNumber }}</td>
                  </tr>
                  <tr>
                    <td>UserId/sub</td>
                    <td>{{ userId }}</td>
                  </tr>
                  <tr>
                    <td>User name</td>
                    <td>{{ username }}</td>
                  </tr>
                  <tr>
                    <td>Access Token</td>
                    <td>{{ accessToken }}</td>
                  </tr>
                  <tr>
                    <td>Refresh Token</td>
                    <td>{{ refreshToken }}</td>
                  </tr>
                </tbody>
              </template>
            </v-simple-table>
          </template>
        </v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn color="primary" @click="logOut">
            Log Out
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-col>
  </v-row>
</template>

<script>
import Logo from "~/components/Logo.vue";
import VuetifyLogo from "~/components/VuetifyLogo.vue";

export default {
  components: {
    Logo,
    VuetifyLogo
  },
  data() {
    return {
      email: null,
      phoneNumber: null,
      userId: null,
      username: null,
      accessToken: null,
      refreshToken: null
    };
  },
  created() {
    this.email = this.$auth.user.email;
    this.phoneNumber = this.$auth.user.phone_number;
    this.userId = this.$auth.user.sub;
    this.username = this.$auth.user.username;
    this.accessToken = this.$auth.strategy.token.get();
    this.refreshToken = this.$auth.strategy.refreshToken.get();
  },
  methods: {
    logOut() {
      this.$auth.logout();
    }
  }
};
</script>

Note: I've explicitly shown on the page that the Refresh Token's value is false. This is because that AWS Cognito only returns refresh tokens if the Authorization Code grant is used. You can read more about refresh token in the Amazon Developer docs.

If everything goes according to plan, you should see a program flow similar to the video at the top of this post. I hope this post has been informative and that it can help you get started with Vue/Nuxt and Amazon Cognito.

Thank you for reading. Until next time, keep coding!

Full source code is available on GitHub: https://github.com/Pietervdw/vuenuxt-awscognito