Nuxt.js Fundamental ตอนที่ 8 - การทำระบบ Authentication

Published on

เขียนวันที่ : Aug 24, 2020

LastModified on

(อัพเดท : Mar 19, 2022)

Discord

เชื่อว่าในเกือบทุกๆ Web Application ย่อมมีการทำระบบ Authentication แน่นอน มีระบบ Login ระบบสมัครสมาชิก และระบบเหล่านี้ ก็เป็นระบบเริ่มต้น ที่น่าศึกษา เพราะเป็นพื้นฐานของเว็บไซต์เกือบทั้งหมดต้องมี

รวมถึงคำถามยอดฮิต ว่าเราจะทำระบบ Login ระบบสมาชิก ด้วยภาษา X กับ Database Y ยังไง?

สำหรับตอนนี้จะเป็นการทำ Authentication บน Nuxt.js นะครับ ตัว Concept คล้ายๆ กับทุกๆ Framework ทุกๆ ภาษา เช่น ทำ Authen ด้วย Vue.js หรือ React.js ใช้ APIs หรือ Firebase เป็น API ตัว concept ก็ไม่ต่างกันมากครับ

เอาละ มาเริ่มเลยดีกว่า

รู้จักกับ Nuxt Auth Module

เราจะใช้ @nuxt/auth ในการทำ Authentication นะครับ ข้อดีคือมี built-in หลายๆ อย่างให้เราใช้ (มองคล้ายๆ passport.js ของ Node.js ครับ) เช่น

  • การกำหนด middleware
  • มี strategy ให้เลือก เช่น local หรือ oAuth หรือ Providers อื่นๆ
  • มี built-in function ให้ใช้ เช่น loginWith, logout, setToken, setuser เป็นต้น

ข้อควรรู้ในการใช้ @nuxtjs/auth

  • ต้อง activate Vuex Store ก่อนนะครับ

เริ่มติดตั้ง Nuxt Auth กันเลย (แนะนำใช้ร่วมกับ Nuxt Axios)

npm install @nuxtjs/auth @nuxtjs/axios

ต่อมาที่ไฟล์ nuxt.config.js เพิ่ม modules ลงไป

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

auth: {
  // Options
}

วิธีการใช้งาน Nuxt Auth

มาดู Concept คร่าวๆ กันก่อนนะครับ โดย Nuxt Auth เราจะใช้ผ่าน middleware หรือ global ด้วย nuxt.config.js ก็ได้

  1. Config ค่าต่างๆ ผ่าน nuxt.config.js
  2. ใช้ state ใน Nuxt Auth เพื่อเช็ค loggedIn ว่า login หรือยัง ถ้ายัง ก็ถูก redirect ไป /login เป็นต้น
  3. ใช้ Nuxt Auth Strategy แบบ local เพื่อ authenticate ด้วยการใช้ Email และ Password
  4. สามารถ handle หรือกำหนดว่า Page ไหนเข้าได้ เข้าไม่ได้ ผ่าน middleware

ตัวอย่าง การ config ไฟล์ nuxt.config.js

nuxt.config.js
// optional
axios: {
  baseURL: 'http://127.0.0.1:8888/api',
  credentials: true
},

auth: {
  strategies: {
    local: {
      endpoints: {
        login: { url: 'login', method: 'post', propertyName: 'data.token' },
        user: { url: 'me', method: 'get', propertyName: 'data' },
        logout: false
      }
    }
  },
  redirect: {
    login: '/login'
  }
}
  • กำหนด strategy เป็นแบบ local และ endpoints url ของ login คือ ${BASE_API}/login ซึ่ง BASE_API เรากำหนดผ่าน environment variable หรือกำหนดด้วย axios ก็ได้
  • เราสามารถกำหนด redirect ได้เช่น กัน เช่น ถ้า $auth.isLoggedIn เป็น false ก็ redirect ไปหน้า login ที่เรากำหนด
  • ตัว credentials: true ใน axios คือกำหนดให้ทุกๆ request จะส่ง Authorization header ไปด้วย

ทีนี้เวลาเราจะเข้าถึง Nuxt Auth ก็แค่ใช้

this.$auth;

เช่น เข้าถึง user หรือ isLoggedIn

this.$auth.user;
// มีค่าเท่ากับเข้าถึงผ่าน Vuex
this.$store.state.auth.user;

this.$auth.isLoggedIn;
// เท่ากับ
this.$store.state.auth.isLoggedIn;

หรืออยากเรียก authenticate เพื่อ login ก็แค่ใช้

const payload = {
  data: {
    username: '',
    password: ''
  }
};

this.$auth.loginWith('local', payload);

หรือ setUserToken

this.$auth.setUserToken(token);

middleware

ตัว Middleware เป็นตัวกำหนด ว่าแต่ละหน้า จะเข้าถึงได้ก็ต่อเมื่อ authenticated ผ่านแล้วหรือเปล่า หรือเราจะกำหนดเป็นแบบ global ก็ได้

// nuxt.config.js
router: {
  middleware: ['auth'];
}

แบบนี้ ทุกๆ หน้าจะต้องผ่าน auth middleware ก่อน ถ้าไม่มี this.$auth.isLoggedIn ก็ไม่สามารถเข้าหน้านี้ได้

แต่เราก็สามารถกำหนดแต่ละหน้าได้ เช่น หน้า /login หรือ /register ก็ไม่จำเป็นต้องมี auth middlware จริงมั้ย การกำหนดแต่ละหน้า ก็ทำแบบนี้เลย

<template> </template>

<script>
  export default {
    middleware: 'auth'
  };
</script>

เริ่มลงมือทำ Authentication

เพื่อนๆ อาจจะงงๆ ตัวทฤษฎี หรือหลักการข้างบน มาลองลงมือทำดูดีกว่า จะได้เห็นภาพจริงๆ เอาแบบง่ายๆ ละกันเนาะ เราจะมีแค่

  • /login - หน้าสำหรับกรอก email, password
  • / - หน้าหลักเข้าได้ทุกคน
  • /private - หน้านี้เข้าได้ เฉพาะผ่าน authenticated แล้วเท่านั้น ถ้า request แบบไม่มี authen ก็ redirect ไปหน้า login ทันที

ทีนี้ส่วน API ก็จะมีง่ายๆ แบบนี้

  • POST /api/login - ส่ง email และ password เป็น payload มาเพื่อ login (ได้ user และ token กลับไป)
  • GET /me - request เพื่อ ดึงข้อมูล currentUser โดยส่ง token แนบมากับ headers ด้วย

เมื่อเราได้ concept คร่าวๆ แล้ว ก็มาเริ่มเลย

ไฟล์ server.js ด้านล่าง ก็อปไป แล้ว start server ด้วยคำสั่ง node server.js ได้เลยครับ

server.js
const express = require('express');
const cors = require('cors');

const app = express();
const router = express.Router();

app.use(cors());
app.use(express.json());

const user = {
  id: 1,
  username: 'john',
  email: 'john@doe.com',
  name: 'John Doe'
};

router.get('/me', (req, res) => {
  return res.json({
    data: {
      user
    }
  });
});

router.post('/login', (req, res) => {
  const { email, password } = req.body;

  // query db.

  if (email === 'admin@admin.com' && password === '123456') {
    return res.json({
      data: {
        user,
        token: 'THIS_IS_TOKEN'
      }
    });
  } else {
    return res.status(401).json({
      message: 'Invalid Password'
    });
  }
});

app.use('/api', router);

app.listen(12345, () => {
  console.log('Mock API start on port 12345');
});

และ install

npm install express cors --save-dev

Note : ตัว Server เป็นแค่ Example เฉยๆนะครับ ไม่มี logic หรือ validate ใดๆ แค่รับ request และ response กลับไปเท่านั้น

สามารถใช้ Project เดิม หรือขึ้นใหม่ด้วย create-nuxt-app ก็ได้ครับ

สร้างไฟล์ pages/login.vue

pages/login.vue
<template>
  <form @submit="login">
    <input type="email" name="email" v-model="email" />
    <input type="password" name="password" v-model="password" />

    <button type="submit">Login</button>
  </form>
</template>

<script>
  export default {
    data() {
      return {
        email: '',
        password: ''
      };
    },
    methods: {
      async login(e) {
        e.preventDefault();

        const payload = {
          email: this.email,
          password: this.password
        };

        try {
          await this.$auth.loginWith('local', {
            data: payload
          });
          this.$router.push('/');
        } catch (e) {
          this.$router.push('/login');
        }
      }
    }
  };
</script>

ต่อมากำหนด nuxt.config.js ให้ตรง และกำหนด baseURL เป็น port 12345 (ที่เรารัน server บน localhost)

nuxt.config.js
axios: {
  baseURL: 'http://localhost:12345/api'
},

auth: {
  strategies: {
    local: {
      endpoints: {
        login: { url: 'login', method: 'post', propertyName: 'data.token' },
        user: { url: 'me', method: 'get', propertyName: 'data.user' },
        logout: false
      }
    }
  },
  redirect: {
    login: '/login'
  }
}

ลองเปิดหน้าเว็บ http://localhost:3000/login และลอง login เข้าสู่ระบบ ดูครับ

อย่าลืม activate Vuex store ด้วยนะครับ หากนึกไรไม่ออก ก็สร้างไฟล์ store/index.js ข้างในมีแค่นี้ได้

export const state = () => ({
  myState: 'Hello'
});

ต่อมาสร้างไฟล์ pages/private.vue ขึ้นมา กำหนด middleware: 'auth' ให้มันซะ เข้าถึงได้เฉพาะ login user.

pages/private.vue
<template>
  <div>
    <h1>THiS IS PRIVATE</h1>

    <nuxt-link to="/">Go to Home</nuxt-link>
  </div>
</template>

<script>
  export default {
    middleware: 'auth'
  };
</script>

หน้า Index ลองเพิ่ม logic นิดนึง ถ้า login เรียบร้อย ให้แสดง Hello และมีปุ่ม Logout แต่ถ้าไม่ได้ login ก็มีปุ่ม Login ให้กดไปหน้า Login นั่นเอง

<template>
  <div class="container">
    <div>
      <Logo />
      <h1 class="title">nuxt auth example</h1>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          rel="noopener noreferrer"
          class="button--green"
        >
          Documentation
        </a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          rel="noopener noreferrer"
          class="button--grey"
        >
          GitHub
        </a>
      </div>

      <hr class="divider" />

      <div v-if="loggedIn">
        <h1>Hello, {{ user.email }}</h1>
        <button @click="logout" class="button--grey">Logout</button>
      </div>

      <div v-else>
        <nuxt-link to="/login" class="button--grey">Login</nuxt-link>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        user: this.$auth.user,
        loggedIn: this.$auth.loggedIn
      };
    },
    methods: {
      async logout() {
        await this.$auth.logout();
        this.$router.push('/login');
      }
    }
  };
</script>

<style>
  .divider {
    margin: 2em 0;
  }

  .container {
    margin: 0 auto;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
      'Helvetica Neue', Arial, sans-serif;
    display: block;
    font-weight: 300;
    font-size: 100px;
    color: #35495e;
    letter-spacing: 1px;
  }

  .subtitle {
    font-weight: 300;
    font-size: 42px;
    color: #526488;
    word-spacing: 5px;
    padding-bottom: 15px;
  }

  .links {
    padding-top: 15px;
  }
</style>

ทดลองเล่นใหม่ เข้าเว็บ http://localhost:3000 และลองดู flow การทำงานดูครับ

เราก็จะได้ concept คร่าวๆ ของการทำ Authentication ฝั่ง Nuxt.js กันนะครับ

Nuxt Auth Done

Hints & Questions?

  • เพื่อนๆ ลองนำไปประยุกต์ใช้งานกันดูนะครับ อาจจะเห็นว่า Tutorial รวบรัดหรือไม่ครบ แต่จริงๆ อยากให้เพื่อนๆ ได้ลองเล่น ลองทำด้วยตัวเองดู ติดปัญหาตรงไหน ก็ค่อยๆ งม ค่อยๆทำนะครับ
  • ลองทำ Server API เอง หรือ หา API อื่นๆ เช่น Firebase Auth หรือ Auth0 มาใช้แทน Server API ดูครับ
  • อยากเก็บ token แบบมี accessToken, refreshToken ทำยังไงนะ?
  • ลองใช้ strategy อื่นๆ ดูเช่น Facebook หรือ Github
  • ถ้า Server API เราเป็นแบบ session เราจะกำหนดยังไง disable token ได้มั้ย?
  • ลองสังเกต localStorage ว่า token เก็บไว้ชื่อว่าอะไร เปลี่ยนชื่อ หรือเปลี่ยน prefix ยังไงนะ?
  • ในตัวอย่าง ตอน GET /me ไม่ได้ส่ง Authorization header ไปด้วย ถ้าจะส่งไปด้วย ต้องไปกำหนด nuxt.config.js ยังไง?
Buy Me A Coffee
Discord