پیاده‌سازی احراز هویت در یک برنامه Nuxt.js
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 18 دقیقه

پیاده‌سازی احراز هویت در یک برنامه Nuxt.js

در این آموزش، نحوه پیاده‌سازی احراز هویت در یک برنامه Nuxt.js با استفاده از ماژول Auth را به شما نشان خواهم داد. دقت کنید که برای این آموزش، از JWT برای احراز هویت استفاده خواهیم کرد.

جدول محتویات:

  1. چیزی که خواهیم ساخت
  2. ساخت سریع یک API
  3. ساخت یک برنامه Nuxt.js
  4. نصب ماژول‌های Nuxt.js ضروری
  5. ساخت یک کامپوننت Navbar (نوار راهنما)
  6. ثبت نام کاربر
  7. نمایش این که یک کاربر وارد شده است، یا نه
  8. ورود کاربر
  9. نمایش پروفایل کاربر
  10. خارج کردن کاربر
  11. محدود کردن صفحه پروفایل به کاربران وارد شده
  12. ساخت یک middleware مهمان
  13. نتیجه گیری

چیزی که خواهیم ساخت:

تصویر زیر، پیش‌نمایشی از چیزی است که خواهیم ساخت.

ساخت سریع یک API

برای جلوگیری از هدر رفتن وقت، یک API که برای این آموزش جمع کرده‌ام را کپی (clone) می‌کنیم:

git clone https://github.com/ammezie/jwt-auth-api.git

سپس Depndencyهای این API را نصب می‌کنیم:

cd jwt-auth-api
npm install

سپس، .env.example را به مجددا نام‌گذاری کرده، و به .env تغییر دهید و یک APP_KEY ایجاد کنید:

// با adonis CLI
$ adonis key:generate

// بدون adonis CLI
$ node ace key:generate

پس از این که این کار تمام شد، عملیات‌های انتقال (migration) را انجام دهید:

// با adonis CLI
$ adonis migration:run

// بدون adonis CLI
$ node ace migration:run

قبل از این که به سراغ ساخت برنامه Nuxt.js برویم، بیاید نگاهی سریع به API مورد نظر داشته باشیم. این API با استفاده از AdoisJs ساخته شده است و از JWT برای احراز هویت استفاده می‌کند؛ و همچنین از SQLite نیز استفاده می‌کند.

این API، سه endpoint دارد:

  • /register: endpoint مربوط به ثبت نام کاربران
  • /login: endpoint مربوط به احراز هویت کاربران
  • /me: endpoint مربوط به دریافت جزئیات درباره کاربر احراز هویت شده فعلی، که توسط middleware به نام auth حفاظت شده است، که این یعنی یک کاربر باید برای دسترسی به این endpoint وارد شده باشد.

این API به طور پیشفرض CORS را به صورت فعال دارد.

حال می‌توانیم این API را شروع کنیم:

$ npm start

باید بتوانیم به این API بر روی آدرس http://127.0.0.1:3333/api دسترسی داشته باشیم. فعلا آن را به صورت اجرا شده رها می‌کنیم.

نکته: گرچه برای این اموزش از API ساخته شده توسط AdonisJs استفاده شده است، می‌توانید از هر فریم‌وورک که خوتان می‌خواهید، استفاده کنید.

ساخت یک برنامه Nuxt.js

برای این کار، Vue CLI را به کار می‌گیریم؛ پس اگر آن را از قبل بر روی سیستم خود نصب ندارید، به این صورت آن را نصب کنید:

$ npm install -g vue-cli

سپس یک برنامه Nuxt.js می‌سازیم:

$ vue init nuxt/starter nuxt-auth

پس از آن، باید Dependencyهای مورد نیاز را نصب کنیم:

$ cd nuxt-auth

$ npm install

حال می‌توانیم برنامه را اجرا کنیم:

$ npm run dev

برنامه باید بر روی http://localhost:3000 اجرا شود.

نصب ماژول‌های Nuxt.js ضروری

حال، بیاید ماژول‌های Nuxt.js که برای برنامه خود نیاز خواهیم داشت را نصب کنیم. از آنجایی که ماژول‌های احراز هویت از Axios به صورت داخلی استفاده می‌کنند، ما از Nuxt Auth module و Nuxt Axios module استفاده خواهیم کرد.

$ npm install @nuxtjs/auth @nuxtjs/axios --save

پس از این که این کار انجام شد، کد زیر را به فایل nuxt.config.js اضافه کنید:

// nuxt.config.js

modules: [

  '@nuxtjs/axios',

  '@nuxtjs/auth'

],

سپس، باید ماژول‌ها را راه‌اندازی کنید. کد زیر را در فایل nuxt.config.js کپی کنید:

// nuxt.config.js

axios: {

  baseURL: 'http://127.0.0.1:3333/api'

},

auth: {

  strategies: {

    local: {

      endpoints: {

        login: { url: 'login', method: 'post', propertyName: 'data.token' },

        user: { url: 'me', method: 'get', propertyName: 'data' },

        logout: false

      }

    }

  }

}

در اینجا، URL اصلی (یعنی URL ای‌پی‌آی که پیشتر به آن اشاره شد) را که Axios در هنگام علامت‌گذاری درخواست‌ها استفاده خواهد کرد، را تنظیم می‌کنیم. سپس endpointهای احراز هویت را برای استراتژی local که متناظر با endpointهای API ما هستند را تعریف می‌کنیم. در صورت احراز هویت موفقیت‌آمیز، نشانه مورد نظر در پاسخ به دست آمده، به عنوان یک آبجکت token در آبجکت data قابل دسترسی خواهد بود؛ از این رو propertyName را مساوی با data.token قرار می‌دهیم. به طور مشابه، پاسخ دریافت شده از اندپوینت /me داخل آبجکت data قرار خواهد گرفت. در آخر، از آنجایی که API ما هیچ endpointای برای خروج ندارد، مقدار logout را مساوی با false قرار می‌دهیم. ما فقط به سادگی نشانه مورد نظر را وقتی که یک کاربر خارج می‌شود، از حافظه داخلی حذف می‌کنیم.

ساخت یک کامپوننت Navbar (نوار راهنما)

برای استایل‌بندی برنامه خود، از Bulma‌ استفاده خواهیم کرد. فایل nuxt.config.js را باز کنید و کد زیر را داخل آبجکت link کپی کنید که در آبجکت head قرار دارد:

// nuxt.config.js
{
    rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css' 
}

حال، بیایید کامپوننت Navbar را بسازیم. نام فایل AppLogo.vue داخل شاخه components را به Navbar.vue تغییر دهید و محتویات آن را با کد زیر جایگزین کنید:

// components/Navbar.vue

<template>

  <nav class="navbar is-light">

    <div class="container">

      <div class="navbar-brand">

        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>

        <button class="button navbar-burger">

          <span></span>

          <span></span>

          <span></span>

        </button>

      </div>

      <div class="navbar-menu">

        <div class="navbar-end">

          <div class="navbar-item has-dropdown is-hoverable">

            <a class="navbar-link">

              My Account

            </a>

            <div class="navbar-dropdown">

              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>

              <hr class="navbar-divider">

              <a class="navbar-item">Logout</a>

            </div>

          </div>

          <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>

          <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>

        </div>

      </div>

    </div>

  </nav>

</template>

کامپوننت Navbar لینک‌هایی به بخش ورود یا ثبت نام، مشاهده پروفایل و خروج را در خود دارد.

سپس، بیایید طرح پیشفرض را بروزرسانی کنیم، تا بتوانیم از کامپوننت Navbar استفاده کنیم. فایل layouts/default.vue را باز کنید و محتویات آن را با این کد جایگزین کنید:

// layouts/default.vue
<template>
  <div>
    <Navbar/>
    <nuxt/>
  </div>
</template>

<script>
import Navbar from '~/components/Navbar'
export default {
  components: {
    Navbar
  }
}
</script>

همچنین، صفحه خانه را نیز باید بروزرسانی کنیم. فایل pages/index.vue را باز کنید و محتویات آن را با این کد جایگزین کنید:

// pages/index.vue

<template>

  <section class="section">

    <div class="container">

      <h1 class="title">Nuxt Auth</h1>

    </div>

  </section>

</template>

حال برنامه ما باید چنین ظاهری داشته باشد:

ثبت نام کاربر

داخل شاخه pages، فایل جدیدی به نام register.vue بسازید و کد زیر را در آن قرار دهید:

// pages/register.vue

<template>

  <section class="section">

    <div class="container">

      <div class="columns">

        <div class="column is-4 is-offset-4">

          <h2 class="title has-text-centered">Register!</h2>

          <Notification :message="error" v-if="error"/>

          <form method="post" @submit.prevent="register">

            <div class="field">

              <label class="label">Username</label>

              <div class="control">

                <input

                  type="text"

                  class="input"

                  name="username"

                  v-model="username"

                  required

                >

              </div>

            </div>

            <div class="field">

              <label class="label">Email</label>

              <div class="control">

                <input

                  type="email"

                  class="input"

                  name="email"

                  v-model="email"

                  required

                >

              </div>

            </div>

            <div class="field">

              <label class="label">Password</label>

              <div class="control">

                <input

                  type="password"

                  class="input"

                  name="password"

                  v-model="password"

                  required

                >

              </div>

            </div>

            <div class="control">

              <button type="submit" class="button is-dark is-fullwidth">Register</button>

            </div>

          </form>

          <div class="has-text-centered" style="margin-top: 20px">

            Already got an account? <nuxt-link to="/login">Login</nuxt-link>

          </div>

        </div>

      </div>

    </div>

  </section>

</template>

<script>

import Notification from '~/components/Notification'

export default {

  components: {

    Notification,

  },

  data() {

    return {

      username: '',

      email: '',

      password: '',

      error: null

    }

  },

  methods: {

    async register() {

      try {

        await this.$axios.post('register', {

          username: this.username,

          email: this.email,

          password: this.password

        })

        await this.$auth.loginWith('local', {

          data: {

            email: this.email,

            password: this.password

          },

        })

        this.$router.push('/')

      } catch (e) {

        this.error = e.response.data.message

      }

    }

  }

}

</script>

این فایل شامل فرمی با ۳ فیلد است: نام کاربری، ایمیل و رمز عبور. هر فیلد به یک داده متناظر بر روی کامپوننت متصل است. پس از این که این فرم تایید شد، متد register فراخوانی می‌شود. با استفاده از ماژول Axios، یک درخواست post به اندپوینت /register ارسال می‌کنیم و داده‌های کاربر را به همراه آن منتقل می‌کنیم. اگر ثبت نام موفقیت آمیز بود، از ماژول Auth به نام loginWith() استفاده می‌کنیم. سپس کاربر را به صفحه خانه منتقل می‌کنیم. اگر در طی ثبت نام خطایی بروز دهد، مقدار error را مساوی با پیام خطای دریافت شده از پاسخ API قرار می‌دهیم.

اگر خطایی وجود داشته باشد، پیغام خطا توسط کامپوننت Notification، که در ادامه خواهیم ساخت، نمایش داده خواهد شد.

قبل از این که ثبت نام کاربر را آزمایش کنیم، بیایید کامپوننت Notification را بسازیم. فایل جدیدی به نام Notification.vue در شاخه components بسازید و این کد را در آن قرار دهید:

// components/Notification.vue

<template>

  <div class="notification is-danger">

    {{ message }}

  </div>

</template>

<script>

export default {

  name: 'Notification',

  props: ['message']

}

</script>

کامپوننت Notification ویژگی‌ای به نام message را می‌پذیرد، که پیغام خطا است.

حال، می‌توانیم ثبت نام کاربر خود را آزمایش کنیم:

نمایش این که یک کاربر وارد شده است، یا نه

پس از ثبت نام موفقیت آمیز،‌ باید وارد شده باشیم، اما راهی نیست که بتوانیم بفهمیم که وارد شده‌ایم یا نه. پس بیایید این مشکل را با بروزرسانی کامپوننت Navbar و اضافه کردن برخی ویژگی‌های محاسبه شده، حل کنیم.

قبل از انجام این کار، ابتدا بیایید Vuex store را با ساخت فایل indes.js در شاخه store فعال‌سازی کنیم. ماژول Auth، وضعیت احراز هویت کاربر را به همراه جزئيات کاربر داخل Vuex state در آبجکتی به نام auth‌ ذخیره‌سازی می‌کند. پس می‌توانیم با استفاده از دستور this.@store.state.auth.loggedIn ، بررسی کنیم و ببینیم که یک کاربر وارد شده است یا نه، که مقدار true و flase را بر می‌گرداند. به طور مشابه، می‌توانیم جزئیات یک کاربر را با دستور this.@store.state.auth.user، که اگر هیچ کاربری وارد نشده باشد، مقدار null را بر می‌گرداند، دریافت کنیم.

از آنجایی که شاید بخواهیم از ویژگی‌های محاسبه شده در جاهای مختلفی از برنامه خود استفاده کنیم، بیایید دریافت کنندگان store را بسازیم. کد زیر را در فایل store/index.js کپی کنید:

// store/index.js

export const getters = {

  isAuthenticated(state) {

    return state.auth.loggedIn

  },

  loggedInUser(state) {

    return state.auth.user

  }

}

در اینجا، دو دریافت کننده می‌سازیم. اولین مورد (isAuthenticated) وضعیت احراز هویت کاربر، و مورد دوم (loggedInUser) جزئیات کاربر وارد شده را بر می‌گرداند.

سپس، بیایید کامپوننت Navbar را بروزرسانی کنیم تا از دریافت کنندگان استفاده کنیم. محتویات فایل components/Navbar.vue را با کد زیر جایگزین کنید:

// components/Navbar.vue

<template>

  <nav class="navbar is-light">

    <div class="container">

      <div class="navbar-brand">

        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>

        <button class="button navbar-burger">

          <span></span>

          <span></span>

          <span></span>

        </button>

      </div>

      <div class="navbar-menu">

        <div class="navbar-end">

          <div class="navbar-item has-dropdown is-hoverable" v-if="isAuthenticated">

            <a class="navbar-link">

              {{ loggedInUser.username }}

            </a>

            <div class="navbar-dropdown">

              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>

              <hr class="navbar-divider">

              <a class="navbar-item">Logout</a>

            </div>

          </div>

          <template v-else>

            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>

            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>

          </template>

        </div>

      </div>

    </div>

  </nav>

</template>

<script>

import { mapGetters } from 'vuex'

export default {

  computed: {

    ...mapGetters(['isAuthenticated', 'loggedInUser'])

  }

}

</script>

ما با استفاده از عملگرهای انتشار (...)، ویژگی‌های محاسبه شده را می‌سازیم. سپس با استفاده از isAuthenticated، منوی کاربر یا لینک‌های مربوط به ورود یا ثبت نام را بر حسب وارد شده بودن یا نبودن کاربر، نمایش می‌دهیم. همچنین، از loggedInUser برای نمایش نام کاربری کاربر احراز هویت شده استفاده می‌کنیم.

حال، اگر برنامه خود را refresh کنیم، باید چنین چیزی ببینیم:

ورود کاربر

حال، بیایید قابلیت وارد شدن را به کاربران برگردانیم. فایل جدیدی به نام login.vue در شاخه pages بسازید و کد زیر را در آن قرار دهید:

// pages/login.vue

<template>

  <section class="section">

    <div class="container">

      <div class="columns">

        <div class="column is-4 is-offset-4">

          <h2 class="title has-text-centered">Welcome back!</h2>

          <Notification :message="error" v-if="error"/>

          <form method="post" @submit.prevent="login">

            <div class="field">

              <label class="label">Email</label>

              <div class="control">

                <input

                  type="email"

                  class="input"

                  name="email"

                  v-model="email"

                >

              </div>

            </div>

            <div class="field">

              <label class="label">Password</label>

              <div class="control">

                <input

                  type="password"

                  class="input"

                  name="password"

                  v-model="password"

                >

              </div>

            </div>

            <div class="control">

              <button type="submit" class="button is-dark is-fullwidth">Log In</button>

            </div>

          </form>

          <div class="has-text-centered" style="margin-top: 20px">

            <p>

              Don't have an account? <nuxt-link to="/register">Register</nuxt-link>

            </p>

          </div>

        </div>

      </div>

    </div>

  </section>

</template>

<script>

import Notification from '~/components/Notification'

export default {

  components: {

    Notification,

  },

  data() {

    return {

      email: '',

      password: '',

      error: null

    }

  },

  methods: {

    async login() {

      try {

        await this.$auth.loginWith('local', {

          data: {

            email: this.email,

            password: this.password

          }

        })

        this.$router.push('/')

      } catch (e) {

        this.error = e.response.data.message

      }

    }

  }

}

</script>

این مورد، بسیار شبیه به صفحه register است. این فرم شامل دو فیلد است: ایمیل و رمز عبور. پس از این که فرم تایید شد، متد login فراخوانی می‌شود. با استفاده از ماژول Auth به نام loginWith()، و منتقل کردن داده‌های کاربر به همراه آن، کاربر را وارد می‌کنیم. اگر احراز هویت موفقیت‌آمیز بود، کاربر را به صفحه خانه منتقل می‌کنیم. در غیر این صورت، مقدار error را مسای با خطای دریافت شده از پاسخ API قرار می‌دهیم. در اینجا باز هم از کامپوننت Notification که پیشتر ساختیم، برای نمایش پیغام خطا استفاده می‌کنیم.

نمایش پروفایل کاربر

بیایید به کاربر وارد شده اجازه دهیم که بتواند پروفایل خود را ببیند. فایل جدیدی به نام profile.vue در شاخه pages بسازید و کد زیر را در آن قرار دهید:

// pages/profile.vue

<template>

  <section class="section">

    <div class="container">

      <h2 class="title">My Profile</h2>

      <div class="content">

        <p>

          <strong>Username:</strong>

          {{ loggedInUser.username }}

        </p>

        <p>

          <strong>Email:</strong>

          {{ loggedInUser.email }}

        </p>

      </div>

    </div>

  </section>

</template>

<script>

import { mapGetters } from 'vuex'

export default {

  computed: {

    ...mapGetters(['loggedInUser'])

  }

}

</script>

از آنجایی که در حال استفاده از دریافت کننده loggedInUser که پیشتر برای نمایش جزئیات کاربر استفاده کردیم هستیم، درک این کد بسیار ساده است.

کلیک بر روی لینک My Profile باید چنین نتیجه‌ای را نمایش دهد:

خارج کردن کاربر

لینک خروج داخل کامپوننت Navbar را به این صورت بروزرسانی کنید:

// components/Navbar.vue
<a class="navbar-item" @click="logout">Logout</a>

وقتی که بر روی لینک خروج کلیک شود، این لینک متد logout را فعال می‌کند.

سپس، بیایید متد logout را به بخش اسکریپت کامپوننت Navbar اضافه کنیم.

// components/Navbar.vue

methods: {

  async logout() {

    await this.$auth.logout();

  },

},

ما متد logout() از ماژول Auth را فراخوانی می‌کنیم. این کار، به سادگی نشانه کاربر را از حافظه داخلی حذف می‌کند و کاربر را به صفحه خانه منتقل می‌کند.

محدود کردن صفحه پروفایل به کاربران وارد شده

در حال حاضر، هر کسی می‌تواند صفحه profile را بررسی کند و اگر کاربر وارد نشده است، با پیغام زیر مواجه می‌شود:

برای رفع این مشکل، باید صفحه پروفایل را به کاربران وارد شده محدود کنیم. خوشبختانه، می‌توانیم با استفاده از ماژول Auth، به راحتی این کار را انجام دهید. ماژول Auth، به همراه خود یک middleware به نام auth دارد، که می‌تواند برای این سناریو استفاده شود.

پس بیایید auth را به صفحه profile اضافه کنید. بخش اسکریپت را به این صورت بروزرسانی کنید:

// pages/profile.vue

<script>

...

export default {

  middleware: 'auth',

 ...

}

</script>

حال وقتی که یک کاربر وارد نشده تلاش به بررسی صفحه profile می‌کند، آن کاربر به صفحه login منتقل می‌شود.

ساخت یک middleware مهمان

باز هم در حال حاضر، حتی اگر کاربری وارد شده باشد، همچنان می‌تواند به صفحات ورود و ثبت نام دسترسی داشته باشد. تنها راه برای رفع این مشکل، محدود کردن صفحات ورود و ثبت نام به کاربرانی است که که وارد نشده‌اند. می‌توانیم با ساخت یک middleware به نام guset، این کار را انجام دهیم. در شاخه middleware، فایل جدیدی به نام guest.js بسازید و این کد را در آن قرار دهید:

// middleware/guest.js

export default function ({ store, redirect }) {

  if (store.state.auth.loggedIn) {

    return redirect('/')

  }

}

این middleware متنی را به عنوان آرگومان اول خود می‌گیرد؛ پس ما store و redirect را از متن مربوطه استخراج می‌کنیم. بررسی می‌کنیم که کاربر وارد شده است یا نه، و سپس او را به صفحه خانه منتقل می‌کنیم. در غیر این صورت، درخواست را به صورت معمولی اجرا می‌کنیم.

حال، بیایید از این middleware استفاده کنیم. بخش اسکریپت login و register را به این صورت بروزرسانی کنید:

<script>

...

export default {

  middleware: 'guest',

 ...

}

</script>

حال همه چیز باید به صورت دلخواه اجرا شود.

نتیجه گیری

کار ما تمام شد! در این آموزش، دیدیم که چگونه می‌توانیم احراز هویت را در یک برنامه Nuxt.js با استفاده از ماژول Auth پیاده‌سازی کنیم. همچنین دیدیم که چگونه می‌توانیم جریان احراز هویت را با استفاده از middlewareها، ثابت نگه داریم.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
1 از 1 رای

/@er79ka

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید