Implementing Authentication in a Nuxt.js App

In this tutorial, I’ll be showing you to implement authentication in Nuxt.js app using the Auth module. For the purpose of this tutorial we’ll be using JWT for authentication.

What we’ll be building

Below is a quick demo of what we’ll be building in this tutorial.

Table of Contents

    Spinning up a quick API

    To save ourselves some time, we’ll clone an API, which I have put together for this tutorial:

    $ git clone https://github.com/ammezie/jwt-auth-api.git

    Then we install the API dependencies:

    $ cd jwt-auth-api
    $ npm install

    Next, rename .env.example to .env and generate an APP_KEY:

    // with the adonis CLI
    $ adonis key:generate
    
    // without the adonis CLI
    $ node ace key:generate

    Once that’s done, let’s run the migrations:

    // with the adonis CLI
    $ adonis migration:run
    
    // without the adonis CLI
    $ node ace migration:run

    Before we move on to building the Nuxt.js app, let’s quickly go over the API. The API is built using AdonisJs and it uses JWT (JSON Web Tokens) for authentication. It also uses SQLite.

    The API has three endpoints:

    • /register: endpoint for users registration.
    • /login: endpoint for authenticating users.
    • /me: endpoint for getting details for the currently authenticated user and it is protected by an auth middleware, which means a user must be authenticated to access the endpoint.

    The API also already has CORS enabled.

    Now, we can start the API:

    $ npm start

    We should be able to access the API on http://127.0.0.1:3333/api. We’ll leave it running.

    Note: Though I’ll be using an API built with AdonisJs for the purpose of this tutorial, you are free to use whatever framework that work best for you.

    Creating a Nuxt.js app

    For this, we’ll make use of the Vue CLI, so you need to first install the Vue CLI in case you don’t have it installed already:

    $ npm install -g vue-cli

    Then we can create a Nuxt.js app:

    $ vue init nuxt/starter nuxt-auth

    Next, we need to install the dependencies:

    $ cd nuxt-auth
    $ npm install

    We can launch the app:

    $ npm run dev

    The app should be running on http://localhost:3000.

    Installing necessary Nuxt.js modules

    Now, let’s install the Nuxt.js modules that we’ll be needing for our app. We’ll be using the Nuxt Auth module and the Nuxt Axios module, since auth module makes use of Axios internally:

    $ npm install @nuxtjs/auth @nuxtjs/axios --save

    Once that’s done, add the code below to nuxt.config.js:

    // nuxt.config.js
    
    modules: [
      '@nuxtjs/axios',
      '@nuxtjs/auth'
    ],

    Next, we need to set up the modules. Paste the code below into nuxt.config.js

    // nuxt.config.js
    
    axios: {
      baseURL: 'http://127.0.0.1:3333/api'
    },
    
    auth: {
      strategies: {
        local: {
          endpoints: {
            login: { url: 'login', method: 'post', propertyName: 'data.token' },
            user: { url: 'me', method: 'get', propertyName: 'data' },
            logout: false
          }
        }
      }
    }

    Here, we set the base URL (which is that of our API from earlier on) that Axios will use when making requests. Then we define the authentication endpoints for the local strategy corresponding to those on our API. On successful authentication, the token will be available in the response as a token object inside a data object, hence why we set propertyName to data.token. Similarly, the response from the /me endpoint will be inside a data object. Lastly, we set logout to false since our API doesn’t have an endpoint for logout. We’ll just remove the token from localstorage when a user logs out.

    Creating a Navbar component

    To style our app, we’ll be making use of Bulma. Open nuxt.config.js and paste the code below within the link object that is inside the head object:

    // nuxt.config.js
    
    {
        rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css' 
    }

    Now, let’s create the Navbar component. Rename AppLogo.vue inside the components directory to Navbar.vue and replace it content with the following:

    // components/Navbar.vue
    
    <template>
      <nav class="navbar is-light">
        <div class="container">
          <div class="navbar-brand">
            <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
            <button class="button navbar-burger">
              <span></span>
              <span></span>
              <span></span>
            </button>
          </div>
          <div class="navbar-menu">
            <div class="navbar-end">
              <div class="navbar-item has-dropdown is-hoverable">
                <a class="navbar-link">
                  My Account
                </a>
                <div class="navbar-dropdown">
                  <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
                  <hr class="navbar-divider">
                  <a class="navbar-item">Logout</a>
                </div>
              </div>
              <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
              <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
            </div>
          </div>
        </div>
      </nav>
    </template>

    The Navbar component contains links to login or register, links to view profile or logout.

    Next, let’s update the default layout to make use of the Navbar component. Open layouts/default.vue and replace it content with the following:

    // layouts/default.vue
    
    <template>
      <div>
        <Navbar/>
        <nuxt/>
      </div>
    </template>
    
    <script>
    import Navbar from '~/components/Navbar'
    
    export default {
      components: {
        Navbar
      }
    }
    </script>

    Also, let’s update the homepage. Open pages/index.vue and replace it content with the following:

    // pages/index.vue
    
    <template>
      <section class="section">
        <div class="container">
          <h1 class="title">Nuxt Auth</h1>
        </div>
      </section>
    </template>

    Our app should now look something similar to below:

    User registration

    Inside the pages directory, create a new register.vue file and paste the code below in it:

    // pages/register.vue
    
    <template>
      <section class="section">
        <div class="container">
          <div class="columns">
            <div class="column is-4 is-offset-4">
              <h2 class="title has-text-centered">Register!</h2>
    
              <Notification :message="error" v-if="error"/>
    
              <form method="post" @submit.prevent="register">
                <div class="field">
                  <label class="label">Username</label>
                  <div class="control">
                    <input
                      type="text"
                      class="input"
                      name="username"
                      v-model="username"
                      required
                    >
                  </div>
                </div>
                <div class="field">
                  <label class="label">Email</label>
                  <div class="control">
                    <input
                      type="email"
                      class="input"
                      name="email"
                      v-model="email"
                      required
                    >
                  </div>
                </div>
                <div class="field">
                  <label class="label">Password</label>
                  <div class="control">
                    <input
                      type="password"
                      class="input"
                      name="password"
                      v-model="password"
                      required
                    >
                  </div>
                </div>
                <div class="control">
                  <button type="submit" class="button is-dark is-fullwidth">Register</button>
                </div>
              </form>
    
              <div class="has-text-centered" style="margin-top: 20px">
                Already got an account? <nuxt-link to="/login">Login</nuxt-link>
              </div>
            </div>
          </div>
        </div>
      </section>
    </template>
    
    <script>
    import Notification from '~/components/Notification'
    
    export default {
      components: {
        Notification,
      },
    
      data() {
        return {
          username: '',
          email: '',
          password: '',
          error: null
        }
      },
    
      methods: {
        async register() {
          try {
            await this.$axios.post('register', {
              username: this.username,
              email: this.email,
              password: this.password
            })
    
            await this.$auth.loginWith('local', {
              data: {
                email: this.email,
                password: this.password
              },
            })
    
            this.$router.push('/')
          } catch (e) {
            this.error = e.response.data.message
          }
        }
      }
    }
    </script>

    This contains a form with three fields: username, email and password. Each field is bind to a corresponding data on the component. When the form is submitted, a register method will be called. Using the Axios module, we make a post request to the /register endpoint, passing along the user data. If the registration was successful, we make use of the Auth module’s loginWith(), using the local strategy and passing the user data to log the user in. Then we redirect the user to the homepage. If there is an error during the registration, we set the error data as the error message gotten from the API response.

    If there is an error, the error message is displayed by a Notification component, which we’ll create shortly.

    Before we test the user registration out, let’s create the Notification component. Create a new Notification.vue file inside components and paste the code below in it:

    // components/Notification.vue
    
    <template>
      <div class="notification is-danger">
        {{ message }}
      </div>
    </template>
    
    <script>
    export default {
      name: 'Notification',
      props: ['message']
    }
    </script>

    The Notification component accepts a message props, which is the error message.

    Now, we can test out user registration:

    Showing whether a user is logged in or not

    Upon successful registration, we should be logged in but there is no way for us to know whether we are logged in or not for now. So let’s fix that by updating the Navbar component and adding some computed properties.

    Before we do just that, let’s first activate the Vuex store by creating an index.js file inside the store directory. The Auth module stores user authentication status as well as user details inside Vuex state in an auth object. So we can check if a user is logged in or not with this.$store.state.auth.loggedIn, which will either return true or false. Similarly, we can get a user details with this.$store.state.auth.user, which will be null if no user is logged in.

    Tips: We can also access the user authentication status as well as the user details directly with the Auth module using this.$auth.loggedIn and this.$auth.user respectively.

    Since we might want to use the computed properties in multiple places in our app, let’s create store getters. Paste the code below into store/index.js:

    // store/index.js
    
    export const getters = {
      isAuthenticated(state) {
        return state.auth.loggedIn
      },
    
      loggedInUser(state) {
        return state.auth.user
      }
    }

    Here, we create two getters. The first one (isAuthenticated) will return the authentication status of a user and the second (loggedInUser) will return the details or the logged in user.

    Next, let’s update the Navbar component to make use of the getters. Replace the content of components/Navbar.vue with the following:

    // components/Navbar.vue
    
    <template>
      <nav class="navbar is-light">
        <div class="container">
          <div class="navbar-brand">
            <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
            <button class="button navbar-burger">
              <span></span>
              <span></span>
              <span></span>
            </button>
          </div>
          <div class="navbar-menu">
            <div class="navbar-end">
              <div class="navbar-item has-dropdown is-hoverable" v-if="isAuthenticated">
                <a class="navbar-link">
                  {{ loggedInUser.username }}
                </a>
                <div class="navbar-dropdown">
                  <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
                  <hr class="navbar-divider">
                  <a class="navbar-item">Logout</a>
                </div>
              </div>
              <template v-else>
                <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
                <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
              </template>
            </div>
          </div>
        </div>
      </nav>
    </template>
    
    <script>
    import { mapGetters } from 'vuex'
    
    export default {
      computed: {
        ...mapGetters(['isAuthenticated', 'loggedInUser'])
      }
    }
    </script>

    We create the computed properties by using the spread operator () to extract the getters from mapGetters. Then using isAuthenticated, we display the user menu or links to login or register depending on whether the user is logged in or not. Also, we use loggedInUser to display the authenticated user username.

    Now, if we give our app a refresh, we should see something similar to below:

    User log in

    Now let’s allow returning users ability to login. Create a new login.vue file inside the pages directory and paste the code below in it:

    // pages/login.vue
    
    <template>
      <section class="section">
        <div class="container">
          <div class="columns">
            <div class="column is-4 is-offset-4">
              <h2 class="title has-text-centered">Welcome back!</h2>
    
              <Notification :message="error" v-if="error"/>
    
              <form method="post" @submit.prevent="login">
                <div class="field">
                  <label class="label">Email</label>
                  <div class="control">
                    <input
                      type="email"
                      class="input"
                      name="email"
                      v-model="email"
                    >
                  </div>
                </div>
                <div class="field">
                  <label class="label">Password</label>
                  <div class="control">
                    <input
                      type="password"
                      class="input"
                      name="password"
                      v-model="password"
                    >
                  </div>
                </div>
                <div class="control">
                  <button type="submit" class="button is-dark is-fullwidth">Log In</button>
                </div>
              </form>
              <div class="has-text-centered" style="margin-top: 20px">
                <p>
                  Don't have an account? <nuxt-link to="/register">Register</nuxt-link>
                </p>
              </div>
            </div>
          </div>
        </div>
      </section>
    </template>
    
    <script>
    import Notification from '~/components/Notification'
    
    export default {
      components: {
        Notification,
      },
    
      data() {
        return {
          email: '',
          password: '',
          error: null
        }
      },
    
      methods: {
        async login() {
          try {
            await this.$auth.loginWith('local', {
              data: {
                email: this.email,
                password: this.password
              }
            })
    
            this.$router.push('/')
          } catch (e) {
            this.error = e.response.data.message
          }
        }
      }
    }
    </script>

    This is quite similar to the register page. The form contains two fields: email and password. When the form is submitted, a login method will be called. Using the Auth module loginWith() and passing along the user data, we log the user in. If the authentication was successful, we redirect the user to the homepage. Otherwise set error to the error message gotten from the API response. Again, we are using the Notification component from earlier on to display the error message.

    Displaying the user profile

    Let’s allow logged in user to be able to view their profile. Create a new profile.vue file inside the pages directory and paste the code below in it:

    // pages/profile.vue
    
    <template>
      <section class="section">
        <div class="container">
          <h2 class="title">My Profile</h2>
          <div class="content">
            <p>
              <strong>Username:</strong>
              {{ loggedInUser.username }}
            </p>
            <p>
              <strong>Email:</strong>
              {{ loggedInUser.email }}
            </p>
          </div>
        </div>
      </section>
    </template>
    
    <script>
    import { mapGetters } from 'vuex'
    
    export default {
      computed: {
        ...mapGetters(['loggedInUser'])
      }
    }
    </script>

    This is pretty straightforward, as we are using the loggedInUser getter from earlier on to display display the user details.

    Clicking on the My Profile link should result in something similar to below:

    Logging user out

    Update the logout link inside the Navbar component as below:

    // components/Navbar.vue
    
    <a class="navbar-item" @click="logout">Logout</a>

    When the logout link is click, it will trigger a logout method.

    Next, let’s add the logout method inside the script section of the Navbar component:

    // components/Navbar.vue
    
    methods: {
      async logout() {
        await this.$auth.logout();
      },
    },

    We call the logout() of the Auth module. This will simply delete the user’s token from localstorage and redirect the user to the homepage.

    Restricting the profile page to only logged in users

    As it stands now, anybody can visit the profile page and if the user is not logged in, it will result in error as below:

    To fix this, we need to restrict the profile page to only logged in users. Luckily for us, we can easily achieve that with the Auth module. The Auth module comes with an auth middleware, which we can use in this scenario.

    So let’s add the auth middleware to the profile page, update the script section as below:

    // pages/profile.vue
    
    <script>
    ...
    
    export default {
      middleware: 'auth',
     ...
    }
    </script>

    Now when a user that is not logged in tries to visit the profile page, the user will be redirected to the login page.

    Creating a guest middleware

    Again as it stand, even as a logged in user, we can still access the login and register pages. One way to fix that is to restrict login and register pages to only users that are not logged in. We can do that by creating a guest middleware. Inside the middleware directory, create a new guest.js file and paste the code below in it:

    // middleware/guest.js
    
    export default function ({ store, redirect }) {
      if (store.state.auth.loggedIn) {
        return redirect('/')
      }
    }

    A middleware accepts the context as it first argument. So we extract store and redirect from the context. We check if the user is logged in then redirect the user to the homepage. Otherwise, we allow the normal execution of the request.

    Next, let’s make use of this middleware. Update the script section of both login and register as below:

    <script>
    ...
    
    export default {
      middleware: 'guest',
     ...
    }
    </script>

    Now everything should be working as expected.

    Conclusion

    That’s it! In this tutorial, we looked at how to implement authentication in a Nuxt.js application using the Auth module. We also saw how to keep the authentication flow sleek by making use of middleware.

    To learn more and the Auth module, checkout the docs.

    Chimezie Enyinnaya

    20 posts

    Software Developer [PHP Laravel JavaScript NodeJS AdonisJS VueJS] | movie lover | run http://openlaravel.com