How to add authentication to your universal Nuxt app using nuxt/auth module?

Recently I was working on a Nuxt.js app and had to add authentication to it. First thing I thought was to use vuex to store two fields in a state:

  • isLoggedIn: a boolean representing whether user is logged in or not
  • loggedInUser: an object containing the user details for the session that we get from server

And then I added a middleware on pages where I wanted to restrict access to logged in users only. The thought process for this approach is right but problem is that when you refresh the page, the state from vuex is lost. In order to handle that, you would need to use localStorage but that would work only if your app is running in spa mode, that is, on client side only. If you are running your app in universal mode (server side rendered) then you will also need to use cookies and write a custom middleware that checks whether it is running on client side or server side and then use localStorage or cookies accordingly. Doing all of this would be a good exercise to learn how everything works but adding it to a project where multiple people are working might not be a great idea in my opinion. Nuxt has an officially supported module just for this purpose. It's the auth module. In this post, I will talk about how to integrate the auth module to your nuxt app to support authentication using email and password.

Assumptions for the server API

We are making the assumption that the API server:

  • Is running on http://localhost:8080/v1
  • Uses cookie based sessions
  • Has a JSON based API
  • Has the following API endpoints:
    • POST /v1/auth/login: accepts email and password in request body and authenticates the user
    • POST /v1/auth/logout: does not need request body and deletes the user session from server
    • GET /v1/auth/profile: returns the logged in user's object

Overview of the steps involved

We will divide this post into following steps:

  • Installation of axios and auth modules
  • Configuration needed in nuxt.config.js
  • Using the state from auth module to check if user is logged in or not and accessing logged in user in our app components
  • Using the auth module to authenticate the user using email and password based authentication
  • Using middleware provided by the auth module to restrict access to pages to logged in users only

Step 1: Install the axios and auth modules

Open the terminal, navigate to the root directory of your project and run the following command:

npm install @nuxtjs/auth @nuxtjs/axios

Step 2: Configure axios and auth modules

Open your nuxt.config.js file, find the modules section and include the axios and auth modules and add their configuration:

  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],

  auth: {
    strategies: {
      local: {
        endpoints: {
          login: {
            url: '/auth/login',
            method: 'post',
            propertyName: false
          },
          logout: { 
            url: '/auth/logout', 
            method: 'post' 
          },
          user: { 
            url: '/auth/profile', 
            method: 'get', 
            propertyName: false 
          }
        },
        tokenRequired: false,
        tokenType: false
      }
    }
  },
  
  axios: {
    baseURL: 'http://localhost:8080/v1',
    credentials: true
  },
  

The auth object here includes the configuration. The auth module supports local strategy as well as OAuth2. Since we only have email and password based authentication in our case, we only need to provide the configuration for local strategy.

The endpoints section is where we specify the details about our API server's endpoints for login, logout and logged in user's profile and each of the config looks like this:

  user: { 
    url: '/auth/profile', 
    method: 'get', 
    propertyName: false 
  }          

url and method should be consistent with your server API. The url here needs to be relative to the baseUrl config. The propertyName tells the auth module which property in the response object to look for. For example, if your API server reponse for GET /auth/profile is like this:

{
  "user": {
    "id: 1,
    "name": "Jon Snow",
    "email": "jon.snow@asoiaf.com"
  }
}

Then you can set the propertyName as user to look for only the user key in the API response. If you want to use the entire API response, you need to set propertyName to false.

Since our API server has cookie based sessions, we are setting the tokenRequired and tokenType to false.

tokenRequired: false,
tokenType: false

For a complete list of options supported by the auth module, you can visit their official documentation here

The axios object in the above config is used to provide the axios configuration. Here, we are setting the following properties:

  axios: {
    baseURL: 'http://localhost:8080/v1',
    credentials: true
  },

baseUrl here is the root url of our API and any relative url that we hit using axios in our app will be relative to this url. Setting credentials as true ensures that we send the authentication headers to the API server in all requests.

Step 3: Activate vuex store in your app

In order to use the auth module, we need to activate vuex store in our application since that's where the session related information will be stored. This can be done by adding any .js file inside the store directory of your app and nuxt will register a namespaced vuex module with the name of the file. Let's go ahead and add a blank file called index.js to the store directory of our app. It's not mandatory to add index.js file. You could have added any file for example xyz.js in the store directory and that would have activated vuex store in your app.

The auth module that we have included in our project will automatically register a namespaced module named auth with the vuex store. And it has the following fields in the state:

  • loggedIn: A boolean denoting if the user is logged in or not
  • user: the user object as received from auth.strategies.local.user endpoint configured in our nuxt.config.js file.
  • strategy: This will be local in our case

It also adds the necessary mutations for setting the state. So, even though we haven't created any auth.js file in the store directory of our app, the auth module has automatically taken care of all this. If it helps to understand, imagine that a file named auth.js is automatically created by auth module in the store directory of your app even though this file doesn't actually exists. This means that using mapState on auth module of your vuex store will work. For example, you can use this in any of your components or pages:

  computed: {
    ...mapState('auth', ['loggedIn', 'user'])
  },

Here is a complete example of a component using these properties:

<template>
  <b-navbar type="dark" variant="dark">
    <b-navbar-brand to="/">NavBar</b-navbar-brand>
    <b-navbar-nav class="ml-auto">
      <b-nav-item v-if="!loggedIn" to="/login">Login</b-nav-item>
      <b-nav-item v-if="!loggedIn" to="/register">Register</b-nav-item>
      <b-nav-item v-if="loggedIn" @click="logout">
        <em>Hello {{ user.name }}</em>
      </b-nav-item>
      <b-nav-item v-if="loggedIn" @click="logout">Logout</b-nav-item>
    </b-navbar-nav>
  </b-navbar>
</template>

<script>
import { mapState } from 'vuex'
export default {
  name: 'NavBar',
  computed: {
    ...mapState('auth', ['loggedIn', 'user'])
  },
  methods: {
    async logout() {
      await this.$auth.logout()
      this.$router.push('/login')
    }
  }
}
</script>

<style></style>

Alternative approach

Instead of using the mapState, you can also reference the loggedIn and user by this.$auth.loggedIn and this.$auth.user. So, in the above example, you could have re-written the computed properties as mentioned below and it would have still worked fine:

  computed: {
    loggedIn() {
      return this.$auth.loggedIn
    },
    user() {
      return this.$auth.user
    }
  },

Step 4: Authenticating user using the auth module

We know how to use the auth module's APIs to check whether a user is logged in or not, or access the logged in user's details. But we haven't yet covered the part of how to authenticate the user. This is done by using the this.$auth.loginWith method provided by the auth module in any of your components or pages. The first argument to this function is the name of the strategy. In our case this will be local. It's an async function which returns a promise. Here is an example of how to use it:

  try {
    await this.$auth.loginWith('local', {
      data: {
        email: 'email@xyz.com'
        password: 'password',
      }
    })
    // do something on success
  } catch (e) {    
    // do something on failure 
  }

So, typically you would have a login page with a form with email and password fields mapped to data of the component using v-model. And once you submit the form, you can run this function to authenticate using the auth module. Here is an example of login page:

<template>
  <div class="row">
    <div class="mx-auto col-md-4 mt-5">
      <b-card>
        <b-form @submit="submitForm">
          <b-form-group
            id="input-group-1"
            label="Email address:"
            label-for="email"
          >
            <b-form-input
              id="email"
              v-model="email"
              type="email"
              required
              placeholder="Enter email"
            ></b-form-input>
          </b-form-group>

          <b-form-group
            id="input-group-2"
            label="Password:"
            label-for="password"
          >
            <b-form-input
              id="password"
              v-model="password"
              type="password"
              required
              placeholder="Enter password"
            ></b-form-input>
          </b-form-group>

          <b-button type="submit" variant="primary">Login</b-button>
        </b-form>
      </b-card>
    </div>
  </div>
</template>

<script>
export default {
  name: 'LoginPage',
  data() {
    return {
      email: '',
      password: ''
    }
  },
  methods: {
    async submitForm(evt) {
      evt.preventDefault()
      const credentials = {
        email: this.email,
        password: this.password
      }
      try {
        await this.$auth.loginWith('local', {
          data: credentials
        })
        this.$router.push('/')
      } catch (e) {
        this.$router.push('/login')
      }
    }
  }
}
</script>

<style></style>

In order to logout a logged in user, you can use the this.$auth.logout method provided by the auth module. This one doesn't need any arguments. Here is an example:

  methods: {
    async logout() {
      await this.$auth.logout()
      this.$router.push('/login')
    }
  }

Step 5: Using auth middleware to restrict access to certain pages

The auth module also provides middleware to restrict access to logged in users. So, for example if you want to restrict the /profile route of your application to logged in users only, you can add the auth middleware to the profile.vue page like this:

export default {
  name: 'ProfilePage',
  middleware: ['auth']
}

For more details on how you can configure your components and pages to use the auth middleware, you can check out the official docs here.

Conclusion and References

This was kind of a getting started post for axios and auth modules with NuxtJS. We only covered the local strategy but the auth module also supports OAuth2 and can be used to support login using Auth0, Facebook, Github and Google. I would definitely recommend checking out the Guide and API section of the auth module:

https://auth.nuxtjs.org/

The axios module also provides us many configuration options. Although we didn't cover much of it in this post, but I would definitely recommend checking out the official docs for that as well:

https://axios.nuxtjs.org/

I hope this post was helpful in understanding the basics of auth module in Nuxt and makes it easier for you to navigate the rest of the official documentation on your own.

Happy coding :-)