Auth Routes

Authenticated routes example with Nuxt.js

Source Code

Example Files

    Documentation

    Nuxt.js can be used to create authenticated routes easily.

    Using Express and Sessions

    To add the sessions feature in our application, we will use express and express-session, for this, we need to use Nuxt.js programmatically.

    First, we install the dependencies:

    yarn add express express-session body-parser whatwg-fetch

    We will talk about whatwg-fetch later.

    Then we create our server.js:

    const { Nuxt, Builder } = require('nuxt')
    const bodyParser = require('body-parser')
    const session = require('express-session')
    const app = require('express')()
    
    // Body parser, to access req.body
    app.use(bodyParser.json())
    
    // Sessions to create req.session
    app.use(session({
      secret: 'super-secret-key',
      resave: false,
      saveUninitialized: false,
      cookie: { maxAge: 60000 }
    }))
    
    // POST /api/login to log in the user and add him to the req.session.authUser
    app.post('/api/login', function (req, res) {
      if (req.body.username === 'demo' && req.body.password === 'demo') {
        req.session.authUser = { username: 'demo' }
        return res.json({ username: 'demo' })
      }
      res.status(401).json({ error: 'Bad credentials' })
    })
    
    // POST /api/logout to log out the user and remove it from the req.session
    app.post('/api/logout', function (req, res) {
      delete req.session.authUser
      res.json({ ok: true })
    })
    
    // We instantiate Nuxt.js with the options
    const isProd = process.env.NODE_ENV === 'production'
    const nuxt = new Nuxt({ dev: !isProd })
    // No build in production
    if (!isProd) {
      const builder = new Builder(nuxt)
      builder.build()
    }
    app.use(nuxt.render)
    app.listen(3000)
    console.log('Server is listening on http://localhost:3000')

    And we update our package.json scripts:

    // ...
    "scripts": {
      "dev": "node server.js",
      "build": "nuxt build",
      "start": "cross-env NODE_ENV=production node server.js"
    }
    // ...

    Note: You'll need to run npm install --save-dev cross-env for the above example to work. If you're not developing on Windows you can leave cross-env out of your start script and set NODE_ENV directly.

    Using the store

    We need a global state to let our application know if the user is connected across the pages.

    To let Nuxt.js use Vuex, we create a store/index.js file:

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    // Polyfill for window.fetch()
    require('whatwg-fetch')
    
    const store = () => new Vuex.Store({
    
      state: {
        authUser: null
      },
    
      mutations: {
        SET_USER: function (state, user) {
          state.authUser = user
        }
      },
    
      actions: {
        // ...
      }
    
    })
    
    export default store
    1. We import Vue and Vuex (included in Nuxt.js) and we tell Vue to use Vuex to let us use $store in our components
    2. We require('whatwg-fetch') to polyfill the fetch() method across all browsers (see fetch repo)
    3. We create our SET_USER mutation which will set the state.authUser to the connected user
    4. We export our store instance to Nuxt.js can inject it to our main application

    nuxtServerInit() action

    Nuxt.js will call a specific action called nuxtServerInit with the context in argument, so when the app will be loaded, the store will be already filled with some data we can get from the server.

    In our store/index.js, we can add the nuxtServerInit action:

    nuxtServerInit ({ commit }, { req }) {
      if (req.session && req.session.authUser) {
        commit('SET_USER', req.session.authUser)
      }
    }

    To make the data method asynchronous, nuxt.js offers you different ways, choose the one you're the most familiar with:

    1. returning a Promise, nuxt.js will wait for the promise to be resolved before rendering the component.
    2. Using the async/await proposal (learn more about it)

    login() action

    We add a login action which will be called from our pages component to log in the user:

    login ({ commit }, { username, password }) {
      return fetch('/api/login', {
        // Send the client cookies to the server
        credentials: 'same-origin',
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          username,
          password
        })
      })
      .then((res) => {
        if (res.status === 401) {
          throw new Error('Bad credentials')
        } else {
          return res.json()
        }
      })
      .then((authUser) => {
        commit('SET_USER', authUser)
      })
    }

    logout() method

    logout ({ commit }) {
      return fetch('/api/logout', {
        // Send the client cookies to the server
        credentials: 'same-origin',
        method: 'POST'
      })
      .then(() => {
        commit('SET_USER', null)
      })
    }

    Pages components

    Then we can use $store.state.authUser in our pages components to check if the user is connected in our application or not.

    Redirect user if not connected

    Let's add a /secret route where only the connected user can see its content:

    <template>
      <div>
        <h1>Super secret page</h1>
        <router-link to="/">Back to the home page</router-link>
      </div>
    </template>
    
    <script>
    export default {
      // we use fetch() because we do not need to set data to this component
      fetch ({ store, redirect }) {
        if (!store.state.authUser) {
          return redirect('/')
        }
      }
    }
    </script>

    We can see in the fetch method that we call redirect('/') when our user is not connected.