Cooking a Deliveroo clone with Nuxt (Vue.js), GraphQL, Strapi and Stripe - Authentication (part 4/7)

This tutorial is part of the « Cooking a Deliveroo clone with Nuxt (Vue.js), GraphQL, Strapi and Stripe »:

Note: the source code is available on GitHub: https://github.com/strapi/strapi-tutorials/tree/master/tutorials/deliveroo-clone-nuxt-strapi-tutorial.

Authentication

At this point, you may have expected to get ready to order. But before that, you need to give the user the possibility to register and login to your app. No worries, Strapi comes to the rescue with its Users & Permissions plugin already installed in your project.

Authentication

To quicken your front-end development, you are going to install the Strapi JavaScript SDK:

/frontend

yarn add strapi-sdk-javascript  
# OR
npm install strapi-sdk-javascript  
  • Create a /frontend/utils/Strapi.js file and copy/paste the following:
import Strapi from 'strapi-sdk-javascript/build/main'

const apiUrl = process.env.API_URL || 'http://localhost:1337'  
const strapi = new Strapi(apiUrl)

export default strapi;  
export { apiUrl }  

Auth store

For this tutorial, you will store user data in cookies.
You have to install js-cookie:

  • Go into your frontend folder
  • Install js-cookie with the following command:
yarn add js-cookie  
# OR
npm i js-cookie  
  • Create a file called auth.js in the /frontend/store folder and copy/paste the following code:

/frontend/store/auth.js

import Cookies from 'js-cookie'

// Defining an empty state
export const state = () => {}

// Create a mutation that set a user to your state and in a 'user' cookie
export const mutations = {  
  setUser(state, user) {
    state.user = user
    Cookies.set('user', user)
  }
}

Why cookies? 🍪

Nothing related to this food tutorial...

Most of the time, progressive web apps store a JSON Web Token (JWT) in the local storage. That works pretty well, and this is what the Strapi JavaScript SDK does by default (it also stores it as a cookie).

The fact is that you would like to display the username in the header (coming later in this tutorial). So you need to store it somewhere.

You could have stored it in the local storage, but since Nuxt.js supports server-side rendering, which does not have access to the local storage, you need to store it in the browser cookies.

Register

Let's create a register form in order to create a user!

  • Create a new file /frontend/pages/users/register.vue and copy/paste the following content:

/frontend/pages/users/register.vue

<template>  
<div>

  <div class="uk-child-width-1-2@m uk-grid">
      <div>
          // Nice image to make this app more beautiful
          <div class="uk-card uk-card-default uk-card-small uk-card-body">
            <img src="https://assets-ouch.icons8.com/preview/294/e25a0374-0657-45d5-98d9-2408473a744c.png" height="500" width="500" class="uk-align-center" alt="">
          </div>
      </div>
      <div>
          <div class="uk-card uk-card-default uk-card-large uk-card-body">

              <form @submit.stop.prevent="handleSubmit">
                  <fieldset class="uk-fieldset">

                      <legend class="uk-legend">Register</legend>

                      <div class="uk-margin">
                        <label class="uk-form-label">Username</label>
                        <input class="uk-input" v-model="username" type="text" placeholder="pbocuse">
                      </div>

                      <div class="uk-margin">
                        <label class="uk-form-label" for="form-stacked-text">Email</label>
                        <input class="uk-input" v-model="email" type="email" placeholder="paul.bocuse@gmail.com">
                      </div>

                      <div class="uk-margin">
                        <label class="uk-form-label" for="form-stacked-text">Password</label>
                        <input class="uk-input" v-model="password" type="password">
                      </div>

                      <div class="uk-margin">
                        <button class="uk-button uk-button-primary uk-width-1-1" :disabled="loading" type="submit">Submit</button>
                      </div>

                      <div class="uk-margin">
                        <p>
                          Already have an account?
                          <router-link :to="{ name: 'users-signin'}">
                            Login
                          </router-link>
                        </p>
                      </div>

                  </fieldset>
              </form>

          </div>
      </div>
  </div>

</div>  
</template>

<script>  
// Import mapMutations in order to call mutations from your store
import { mapMutations } from 'vuex'  
import strapi from '~/utils/Strapi'

export default {  
  data() {
    return {
      email: '',
      password: '',
      username: '',
      loading: false
    }
  },
  methods: {
    // Method that will register your users
    async handleSubmit() {
      try {
        this.loading = true
        const response = await strapi.register(
          this.username,
          this.email,
          this.password
        )
        this.loading = false
        // Call your setUser mutation from your auth.js store file
        this.setUser(response.user)
        this.$router.go(-1)
      } catch (err) {
        this.loading = false
        alert(err.message || 'An error occurred.')
      }
    },
    // Define all your needed mutations, here: auth/setUser
    ...mapMutations({
      setUser: 'auth/setUser'
    })
  }
}
</script>  

In this page, you insert a form which has three inputs: username, email and address. You also defined a method named handleSubmit which uses the Strapi SDK to register the user before redirecting them to the previous page.

Logout

The user must be able to logout, ideally from a button in the header.

  • Add a logout mutation and a username getter in the auth store:

/frontend/store/auth.js

import Cookies from 'js-cookie'

export const state = () => {}

export const mutations = {  
  setUser(state, user) {
    state.user = user
    Cookies.set('user', user)
  },
  // Mutation that you need to add
  logout(state) {
    state.user = null
    Cookies.set('user', null)
  }
}

// Define a getter in order to get your current username from your state
export const getters = {  
  username: state => {
    return state.user && state.user.username
  }
}
  • Modify the Header.vue to get something like this

/frontend/components/Header.vue

<template>  
  <client-only>
  <nav class="uk-navbar-container" uk-navbar>
      <div class="uk-navbar-left">

          <ul class="uk-navbar-nav">
              <li class="uk-active"><router-link tag="a" class="navbar-brand" to="/" exact>Deliveroo clone</router-link></li>
              <li><router-link tag="a" class="navbar-brand" to="/restaurants" exact>Restaurants</router-link></li>
          </ul>

      </div>

      <div class="uk-navbar-right">

          // If you are logged in
          <ul class="uk-navbar-nav" v-if="username">
              <li><a href="#" class="uk-link-reset"><img src="https://png2.cleanpng.com/sh/a7adacc7226d2dc438dafb37913a8ab8/L0KzQYm3V8E2N5tqipH0aYP2gLBuTfVudZZ5ReZxZT3vdbj2Tf1wfppqRehyZHXyd7L0hb1xeppze9d8cz34frryigR1gV58Rdd2bXX3Pb3shB8udZD7gdc2NXK3coG9UMRibGJrfqI3Mkm0RoS7VMYyPWQ2TqY8M0m5R4GCUb5xdpg=/kisspng-emmet-the-lego-movie-videogame-princess-unikitty-w-emmet-lego-movie-5b4b0604ad1ff0.2916344615316433967091.png" class="uk-border-circle" height="40" width="40" alt="">{{ username }}</a></li>
              <li><a href="#" @click="logout">Logout</a></li>
          </ul>

          // If you are not logged in
          <ul class="uk-navbar-nav" v-else>
              <li><a href="/users/register">Signup</a></li>
              <li><a href="/users/signin">Signin</a></li>
          </ul>

      </div>

  </nav>
</client-only>

</template>

<script>  
import { mapMutations } from 'vuex'

export default {  
  computed: {
    // Set your username thanks to your getter
    username() {
      return this.$store.getters['auth/username']
    }
  },
  methods: {
    // Define your needed mutations, here: auth/logout
    ...mapMutations({
      logout: 'auth/logout'
    })
  }
}
</script>  

Try to reload the page and you will see that no changes have been made: You still see the signin and signup links although you registered a user a few minutes ago. This happens because you did not use the auth/setUser mutation on the load page. Since Nuxt.js is rendered server side, you need to do a little trick using the nuxtServerInit action which is invoked when the Nuxt.js server starts:

  • Install cookieparser:
yarn add cookieparser  
# OR
npm install cookieparser  
  • Create an index.js file in the store folder and copy/paste the following code:

/frontend/store/index.js

import cookieparser from 'cookieparser'

export const actions = {  
  nuxtServerInit({ commit }, { req }) {
    let user = null
    if (req && req.headers && req.headers.cookie) {
      const parsed = cookieparser.parse(req.headers.cookie)
      user = (parsed.user && JSON.parse(parsed.user)) || null
    }

    commit('auth/setUser', user)
  }
}

Perfect! You can register again to check if that works but you are going to create a sign in page right now.

Login

  • Create a signin.vue in the pages folder and copy/paste the following code

/frontend/pages/users/signin.vue

<template>  
<div>

  <div class="uk-child-width-1-2@m uk-grid">
      <div>
          <div class="uk-card uk-card-default uk-card-small uk-card-body">
            <img src="https://assets-ouch.icons8.com/preview/457/0b338840-2e33-432e-a547-4d3e5acc960c.png" height="500" width="500" class="uk-align-center" alt="">
          </div>
      </div>
      <div>
          <div class="uk-card uk-card-default uk-card-large uk-card-body">

              <form @submit.stop.prevent="handleSubmit">
                  <fieldset class="uk-fieldset">

                      <legend class="uk-legend">Sign in</legend>

                      <div class="uk-margin">
                        <label class="uk-form-label" for="form-stacked-text">Email</label>
                        <input class="uk-input" v-model="email" type="email" placeholder="paul.bocuse@gmail.com">
                      </div>

                      <div class="uk-margin">
                        <label class="uk-form-label" for="form-stacked-text">Password</label>
                        <input class="uk-input" v-model="password" type="password">
                      </div>

                      <div class="uk-margin">
                        <button class="uk-button uk-button-primary uk-width-1-1" :disabled="loading" type="submit">Submit</button>
                      </div>

                      <div class="uk-margin">
                        <p>
                          No account yet?
                          <router-link :to="{ name: 'users-register'}">
                            Register
                          </router-link>
                        </p>
                      </div>

                  </fieldset>
              </form>

          </div>
      </div>
  </div>

</div>  
</template>

<script>  
import { mapMutations } from 'vuex'  
import strapi from '~/utils/Strapi'

export default {  
  data() {
    return {
      email: '',
      password: '',
      loading: false
    }
  },
  methods: {
    async handleSubmit() {
      try {
        this.loading = true
        const response = await strapi.login(
          this.email,
          this.password
        )
        this.loading = false
        this.setUser(response.user)
        this.$router.go(-1)
      } catch (err) {
        this.loading = false
        alert(err.message || 'An error occurred.')
      }
    },
    ...mapMutations({
      setUser: 'auth/setUser'
    })
  }
}
</script>  

Note: You will be redirected to the last page you visited when you sign in

That's it for the authentication!
- Reload your page and play with this new user system you just created!

🛒 In the next section, you will learn how to create a full featured shopping card: https://blog.strapi.io/cooking-a-deliveroo-clone-with-nuxt-vue-js-graphql-strapi-and-stripe-shopping-card-part-5-7.

News in your inbox

Did you enjoy this article? Subscribe to get the latest posts and the most important updates!