Categories
Nuxt.js

Adding Authentication to a Nuxt App with the Firebase

With the Nuxt Auth module, we can add authentication to our Nuxt app with ease.

One way is to add authentication with Firebase.

In this article, we’ll look at how to add Firebase authentication to our server-side rendered Nuxt app.

Install Packages

We have to add some packages to add Firebase to our Nuxt app.

To do that, we run:

npm i @nuxtjs/firebase @nuxtjs/pwa firebase firebase-admin

to add the required packages.

Configuration

We have to add configuration to our Nuxt app so that we can get the user when we load pages.

In nuxt.config.js , we write:

export default {
  /*
  ** Nuxt rendering mode
  ** See https://nuxtjs.org/api/configuration-mode
  */
  mode: 'universal',
  /*
  ** Nuxt target
  ** See https://nuxtjs.org/api/configuration-target
  */
  target: 'server',
  /*
  ** Headers of the page
  ** See https://nuxtjs.org/api/configuration-head
  */
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  /*
  ** Global CSS
  */
  css: [
  ],
  /*
  ** Plugins to load before mounting the App
  ** https://nuxtjs.org/guide/plugins
  */
  plugins: [
  ],
  /*
  ** Auto import components
  ** See https://nuxtjs.org/api/configuration-components
  */
  components: true,
  /*
  ** Nuxt.js dev-modules
  */
  buildModules: [
  ],
  /*
  ** Nuxt.js modules
  */
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    '@nuxtjs/pwa',
    [
      '@nuxtjs/firebase',
      {
        config: {
          apiKey: "api-key",
          authDomain: "project-id.firebaseapp.com",
          databaseURL: "https://project-id.firebaseio.com",
          projectId: "project-id",
          storageBucket: "project-id.appspot.com",
          messagingSenderId: 'message-sender-id',
          appId: "BookDb"
        },
        services: {
          auth: {
            persistence: 'local',
            initialize: {
              onAuthStateChangedMutation: "SET_USER",
              onAuthStateChangedAction: 'onAuthStateChangedAction',
            },
            ssr: {
              serverLogin: {
                sessionLifetime: 60 * 60 * 1000,
                loginDelay: 50
              }
            }
          }
        }
      }
    ]
  ],
  /*
  ** Axios module configuration
  ** See https://axios.nuxtjs.org/options
  */
  axios: {

},
  /*
  ** Build configuration
  ** See https://nuxtjs.org/api/configuration-build/
  */
  build: {
  },
  firebase: {
    services: {
      auth: {
        ssr: true
      }
    }
  },
  pwa: {
    meta: false,
    icon: false,
    workbox: {
      importScripts: [
        '/firebase-auth-sw.js'
      ],
      dev: true
    }
  }
}

We add the @nuxtjs/firebase module with many options.

The config property has the Firebase configuration options.

services configures the auth service to persist the user.

The onAuthStateChangeMutation action is the Vuex action to save the authenticated user’s data.

ssr has the options for how long to keep the user data.

The sessionLifetime property has the session lifetime.

And loginDelay is the delay to save the user data after a successful login.

Vuex Store

We’ve to create a Vuex store to store the data.

To do that, we create a store/index.js file and write:

export const state = () => ({
  authUser: {}
})

export const actions = {
  async onAuthStateChangedAction({ commit }, { authUser, claims }) {
    const { uid, email, emailVerified, displayName, photoURL } = authUser

  commit('SET_USER', {
      uid,
      email,
      emailVerified,
      displayName,
      photoURL,
      isAdmin: claims.custom_claim
    })
  },
  async nuxtServerInit({ dispatch, commit }, { res }) {
    console.log(res.locals)
    if (res && res.locals && res.locals.user) {
      const { allClaims: claims, idToken: token, ...authUser } = res.locals.user
      await dispatch('onAuthStateChangedAction', {
        authUser,
        claims,
        token
      })
      commit('ON_AUTH_STATE_CHANGED_MUTATION', { authUser, claims, token })
    }
  }
}

export const mutations = {
  ON_AUTH_STATE_CHANGED_MUTATION(state, { authUser, claims }) {
    const { uid, email, emailVerified, displayName, photoURL } = authUser

    state.authUser = {
      uid,
      displayName,
      email,
      emailVerified,
      photoURL: photoURL || null,
      isAdmin: claims.custom_claim
    }
  },
  SET_USER(state, payload) {
    console.log(payload)
    state.authUser = payload;
  }
}

The onAuthStateChanged action is invoked by nuxtServerInit to set the authenticated user’s data on page load.

nuxtServerInit is run when the page loads.

ON_AUTH_STATE_CHANGED_MUTATION is a mutation to save the authUser state.

SET_USER sets the authUser state.

When we run the this.$fireAuth.signInWithEmailAndPassword method is run successful by authentication with the correct credentials, then the actions and mutations will be run.

The res.locals.user property in the nuxtServerInit will also be set so that we have the currently logged in user’s data on page load.

Login Form

Finally, we need a login form so we can log in.

We create a login.vue file and add:

<template>
  <div class="container">
    <form @submit="signIn">
      <div>
        <label>Username</label>
        <input type="text" v-model="login.email" />
      </div>
      <div>
        <label>Password</label>
        <input type="text" v-model="login.password" />
      </div>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      login: {},
    };
  },
  methods: {
    async signIn() {
      try {
        const { email, password } = this.login;
        await this.$fireAuth.signInWithEmailAndPassword(email, password);
      } catch (error) {
        console.log(error);
      }
    },
  },
};
</script>

We create a login form and if we log in with the right email and password, the Vuex actions will be run to set the data.

This is because the static/assets/firebase-auth-sw.js service worker that comes with the Nuxt Firebase does that work for us in the background.

Categories
Nuxt.js

Adding Authentication to a Nuxt App with the Nuxt Auth Module

With the Nuxt Auth module, we can add authentication to our Nuxt app with ease.

In this article, we’ll look at how to add authentication to our Nuxt app with Express and the Nuxt Auth module.

Getting Started

We’ve to add the Axios and Nuxt Auth modules.

Nuxt Auth uses Axios for sending requests.

To install it, we run:

yarn add @nuxtjs/auth @nuxtjs/axios

or:

npm install @nuxtjs/auth @nuxtjs/axios

Then in nuxt.config.js , we add:

const baseURL = "https://ReasonableRecursiveSupercollider--five-nine.repl.co";

export default {
  mode: 'universal',
  target: 'server',
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  css: [
  ],
  plugins: [
  ],
  components: true,
  buildModules: [
  ],
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],
  axios: {
    baseURL
  },
  build: {
  }
}

We add the baseURL to the axios property to set the base URL of our requests.

Also, we add the '@nuxtjs/auth' module to the modules array to add the module.

In the store folder, we’ve to add index.js to use the Nuxt auth module.

Back End

We use Express as the back end for our app.

To create it, we install Express with some packages by running:

npm i express body-parser cors

in a project folder.

Then we create index.js in the same folder and add:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors')
const app = express();
app.use(cors())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/api/auth/login', (req, res) => {
  res.json({});
});

app.post('/api/auth/logout', (req, res) => {
  res.json({});
});

app.get('/api/auth/user', (req, res) => {
  res.json({});
});

app.listen(3000, () => console.log('server started'));

We need the cors middleware to accept cross-domain requests.

The routes are the endpoints we send our auth requests to.

We should add logic to check for users in real-world applications.

Front End

In our Nuxt app, we add a login.vue component and add:

<template>
  <div class="container">
    <form @submit.prevent="userLogin">
      <div>
        <label>Username</label>
        <input type="text" v-model="login.username" />
      </div>
      <div>
        <label>Password</label>
        <input type="text" v-model="login.password" />
      </div>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  </div>
</template>

<script>
export default {
  middleware: "auth",
  data() {
    return {
      login: {},
    };
  },
  auth: {
    strategies: {
      local: {
        endpoints: {
          login: {
            url: `/api/auth/login`,
            method: "post",
            propertyName: "token",
          },
          logout: { url: `/api/auth/logout`, method: "post" },
          user: {
            url: `/api/auth/user`,
            method: "get",
            propertyName: "user",
          },
        },
      },
    },
  },
  methods: {
    async userLogin() {
      try {
        let response = await this.$auth.loginWith("local", {
          data: this.login,
        });
        console.log(response);
      } catch (err) {
        console.log(err);
      }
    },
  },
};
</script>

It has a login form that takes the username and password.

In the component options, we enable the auth middleware with the middleware: 'auth' property.

Then we set the auth options by adding the auth.strategies.local property to add the endpoints, we’ll make requests to.

login is used by the this.$auth.loginWith method to make requests.

auth.strategies.local optionally takes the tokenRequired and tokenType properties.

tokenRequired: false disables all token handling.

tokenType is the authorization header name to be used in Axios requests.

this.$auth.loginWith takes the strategy as the first argument and the data as the 2nd argument with the data property.

So when we submit the form, the userLogin method is called and the request to https://ReasonableRecursiveSupercollider--five-nine.repl.co/api/auth/login is made with the username and password in the request payload.

Conclusion

We can add a login form with the Nuxt and use the auth module to make the requests.

Categories
Nuxt.js

Nuxt.js — Vuex

Nuxt.js is an app framework that’s based on Vue.js.

We can use it to create server-side rendered apps and static sites.

In this article, we’ll look at how to use Vuex with Nuxt.

Activate the Store

Vuex is built into Nuxt.

We can just import it and add the store option to the root Vue instance.

To add the store, we can write:

store/index.js

export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state) {
    state.counter++
  }
}

We created a root module for our Vuex store since the code is in the index.js file.

Then we can use it by writing:

page/counter.vue

<template>
  <div class="container">
    <button @click="increment">increment</button>
    <p>{{counter}}</p>
  </div>
</template>

<script>
export default {
  computed: {
    counter() {
      return this.$store.state.counter;
    },
  },
  methods: {
    increment() {
      this.$store.commit('increment');
    },
  },
};
</script>

We have access to the this.$store object.

The state property has the state.

And the commit method lets us run a mutation.

To create a namespaced module, we can change the name of the store file.

For example, we can create a store/counter.js file and write:

export const state = () => ({
  count: 0
})

export const mutations = {
  increment(state) {
    state.count++
  }
}

Then we can access the counter module by writing:

<template>
  <div class="container">
    <button @click="increment">increment</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
import { mapMutations } from "vuex";

export default {
  computed: {
    count() {
      return this.$store.state.counter.count;
    },
  },
  methods: {
    increment() {
      this.$store.commit('counter/increment');
    },
  },
};
</script>

We add the namespace to access the state and commit our actions.

Also, we can use the map methods from Vuex to map the getters, mutations, and actions to properties in our component.

For example, we can write:

<template>
  <div class="container">
    <button @click="increment">increment</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
import { mapMutations } from "vuex";

export default {
  computed: {
    count() {
      return this.$store.state.counter.count;
    },
  },
  methods: {
    ...mapMutations({
      increment: "counter/increment",
    }),
  },
};
</script>

to map our counter/increment mutation to the increment method with the mapMutations method.

Plugins

We can add Vuex plugins to our Vuex store.

For example, we can add the Vuex logger to our app by writing:

store/index.js

import createLogger from 'vuex/dist/logger'

export const plugins = [createLogger()]

export const state = () => ({
  count: 0
})

export const mutations = {
  increment(state) {
    state.count++
  }
}

We just export the plugins array to add our plugins.

The nuxtServerInit Action

The nuxtServerInit action is defined in the store.

This runs in any environment.

To use it, we can add it to our store by writing:

store/index.js

export const actions = {
  nuxtServerInit({ commit }, { req }) {
    commit('core/load', { foo: 'bar' })
  }
}

store/core.js

export const state = () => ({
  obj: {}
})

export const mutations = {
  load(state, payload) {
    state.obj = payload;
  }
}

foo.vue

<template>
  <div class="container">{{obj}}</div>
</template>

<script>
export default {
  computed: {
    obj() {
      return this.$store.state.core.obj;
    },
  },
};
</script>

We have the nuxtServerInit action in the root module.

It has the commit function to let us commit mutations.

req is the request object.

Conclusion

We can add a Vuex store to an Nuxt app with a few changes.

Categories
Nuxt.js

Nuxt.js — Plugins and Modules

Nuxt.js is an app framework that’s based on Vue.js.

We can use it to create server-side rendered apps and static sites.

In this article, we’ll look at how to use plugins on client and server-side environments and create modules.

Client or Server-Side Plugins

We can configure plugins to be only available on client or server-side.

One way to do this is to add client.js to the file name to create a client-side only plugin.

And we can add server.js to the file name to create a server-side only plugin.

To do this, in nuxt.config.js , we can write:

export default {
  plugins: [
    '~/plugins/foo.client.js',
    '~/plugins/bar.server.js',
    '~/plugins/baz.js'
  ]
}

If there’s no suffix, then the plugin is available in all environments.

We can do the same thing with the object syntax.

For example, we can write:

export default {
  plugins: [
    { src: '~/plugins/both-sides.js' },
    { src: '~/plugins/client-only.js', mode: 'client' },
    { src: '~/plugins/server-only.js', mode: 'server' }
  ]
}

The mode property can be set to 'client' to make the plugin available on the client-side.

To make a plugin available on server-side, we can set the mode to 'server' .

For plugins that are only available on server-side, we can check if process.server is true in the plugin code before we run the code.

Also, we can check if process.static is true before we run the plugin code on static pages.

Nuxt.js Modules

Nuxt.js comes with a few modules that we can use to extend Nuxt’s core functionality.

@nuxt/http is used to make HTTP requests.

@nuxt/content is used to write content and fetch Markdown, JSON, YAML, and CSV files through a MongoDB like API.

@nuxtjs/axios is a module used for Axios integration to make HTTP requests.

@nuxtjs/pwa is used to create PWAs.

@nuxtjs/auth is used for adding authentication.

Write a Module

We can create our own modules.

To add one, we can create a file in the modules folder.

For example, we can create a modules/simple.js file and write:

export default function SimpleModule(moduleOptions) {
  // ...
}

Then we can add the module into nuxt.config.js so that we can use it:

modules: [
  ['~/modules/simple', { token: '123' }]
],

Then object in the 2nd entry is passed into the SimpleModule function as its argument.

Modules may be async.

Build-only Modules

We can create build-only modules and put them in the buildModules array in nuxt.config.js .

For example, we can write:

modules/async.js

import fse from 'fs-extra'

export default async function asyncModule() {
  const pages = await fse.readJson('./pages.json')
  console.log(pages);
}

We added the fs-extra module to read files.

The function is async, so it returns a promise with the resolved value being what we return.

In nuxt.config.js , we add:

buildModules: [
  '~/modules/async'
],

to add our module.

The module will be loaded when we run our dev server or at build time.

Conclusion

We can create modules and plugins that are available on the client or server-side with Nuxt.

Categories
Nuxt.js

Nuxt.js — Error Pages, Async, and Request Data

Nuxt.js is an app framework that’s based on Vue.js.

We can use it to create server-side rendered apps and static sites.

In this article, we’ll look at how to add error pages, get async data, and get request data with Nuxt.js.

Error Page

We can add an error page to our Nuxt app by putting pages files in the layouts folder.

For example, if we want to show a page when we get a 404 error, we add a layouts/error.js file:

<template>
  <div>
    <h1 v-if="error.statusCode === 404">Page not found</h1>
  </div>
</template>

<script>
export default {
  props: ["error"],
};
</script>

It takes an error prop and we can check the statusCode property for the error status code.

Pages

Pages have special attributes and functions to make the development of our universal app.

For example, our pages may look like:

<template>
  <div>hello {{ name }}</div>
</template>

<script>
export default {
  asyncData(context) {
    return { name: "world" };
  },
  fetch() {
    //...
  },
  head() {
    return { foo: "bar" };
  },
};
</script>

The asyncData method lets us set the state before loading the component.

The returned object will be merged with our data object.

The fetch method lets us fill the store before rendering the page.

head lets us set the meta tags for the page.

loading prevents a page from automatically calling this.$nuxt.$loading.finish() as we enter it and this.$nuxt.$loading.start() as we leave it.

This lets us manually control the behavior.

transition defines a specific transition for the page.

scrollToTop is a boolean that specifies if we want to scroll to the top before rendering the page.

validate is a validator function for dynamic routes.

middleware defines the middleware for the page.

Async Data

We can use the asyncData method for getting data.

If we use Axios interceptors in our code, then we have to create an instance of it first.

For example, we can write:

import axios from 'axios'
const myAxios = axios.create({
  // ...
})
myAxios.interceptors.response.use(
  function (response) {
    return response.data
  },
  function (error) {
    // ...
  }
)

In our asyncData method, we can return a promise with it.

For example, we can write:

<template>
  <div>{{ title }}</div>
</template>

<script>
import axios from "axios";

export default {
  async asyncData({ params }) {
    const { data } = await axios.get(
      `https://jsonplaceholder.typicode.com/posts/${params.id}`
    );
    return { title: data.title };
  },
};
</script>

We have the asyncData method that takes an object with the params property.

It has the URL parameters for our page.

Then we can use axios to get the data we want and return the resolved value.

The resolved object can be retrieved from our component.

The Context

The context parameter for asyncData also has the req and res properties.

req has the request object.

And res has the response.

We use the process.server to check if the page is server-side rendered before using the request data.

To do that, we write:

pages/hello.vue

<template>
  <div class="container">{{host}}</div>
</template>

<script>
export default {
  async asyncData({ req, res }) {
    if (process.server) {
      return { host: req.headers.host };
    }
    return {};
  },
};
</script>

If process.server is true , then we can use the req object to get the request data.

Conclusion

We can get request data with Nuxt in our pages.

Also, we can create our own error page.

And we can initial our page with async data.