Sokan Academy

هر زمانی که یک پروژه ی جدی را شروع می‌کنید، به احتمال زیاد با این مسئله روبرو شده اید که چگونه در سمت کاربر احراز هویت را مدیریت کنید. البته روش های مختلفی برای احراز هویت کاربران وجود دارد اما در این مقاله قصد داریم تنها احراز هویت مبتنی بر توکن (token-based authentication) را آموزش دهیم.

برای درک این موضوع باید برای سوال های زیر پاسخ مناسب داشته باشید:

  • توکن کاربران را چطور باید ذخیره کرد؟
  • چگونه کاربر پس از اقدام های احراز هویتی (login / logout) به قسمت موردنظر هدایت شود؟
  • چگونه باید از دسترسی کاربران لاگین نشده به قسمت های مخصوص کاربران لاگین شده، جلوگیری شود؟

این مقاله در مورد پاسخ دادن به سوال های بالا صحبت می کند و تلاش می کند تا با ایجاد کدی تمیز و روش خوب به شما کمک کند. (برای اطلاعات بیشتر در مورد فریمورک Vue js، می توانید به دوره آموزش فریمورک Vue js در سایت سکان آکادمی مراجعه کنید.)

اجازه دهید قبل از شروع بحث، توضیحاتی را در مورد توکن برای شما ارائه دهم. توکن یک رشته طولانی از کارکترها و اعداد است که به نوعی رمز ورود کاربر به سایت می باشد و در صورتی که کاربر آن را نداشته باشد، نمی تواند احراز هویت را انجام دهد. این رشته طولانی با استفاده از کدهای توسعه دهندگان سمت سرور ساخته شده و از طریق API به سمت کاربر فرستاده می شود. در صورتی که کاربر توکن را داشته باشد می تواند آن را در جای مخصوصی ذخیره کرده و تا زمانی که این توکن هنوز از بین نرفته، می تواند به تمام قسمت های سایت (با فرض اینکه دسترسی تمام کاربران به قسمت های مختلف یکسان باشد) دسترسی داشته باشد و در صورت تمایل به خروج از سایت (logout) می تواند توکن خود را از حالت ذخیره خارج کرده و آن را از بین ببرد تا دسترسی های او نیز از بین بروند. برای ذخیره سازی توکن نیز راه های مختلفی وجود دارد که یکی از این راه ها استفاده از localStorage است.

با این حال، این موضوع را در نظر بگیرید که در پروژه های مختلف راه های متفاوتی برای احراز هویت وجود دارد و برای بررسی این موضوع می توان از روش های دیگری نیز استفاده کرد. پروژه می تواند یک صفحه با دسترسی از راه لاگین باشد (مانند Gmail) یا یک اپلیکیشن با دسترسی آزاد (مانند دیجی کالا) باشد. بنابراین بهتر است هر آنچه در این مقاله نوشته می شود، به درستی بهره ببرید.

قبل از شروع، این نکته را در نظر بگیرید که در این مقاله از Vuex استفاده ای نشده است و تنها با بهره گیری از قابلیت های زبان جاوااسکریپت این کار انجام می شود.

لاگین کردن

به صورت کلی، کارهایی که قرار هست با همدیگر انجام دهیم به صورت زیر هستند که مرحله به مرحله هر یک از آن ها را انجام می دهیم:

  1. ساخت یک پروژه ی ساده با استفاده از Vue CLI
  2. پیاده سازی صفحه های موردنظر
  3. ایجاد یک فایل جاوااسکریپتی برای انجام عملیات مختلف روی token ذخیره شده
  4. ذخیره کردن token گرفته شده از سرور
  5. اعمال دسترسی ها به صفحه های مختلف متناسب با نیاز کاربر
  6. تنظیم interceptorها برای ارسال درخواست به سرور

مرحله ی 1: ساخت یک پروژه ی ساده با استفاده از Vue CLI

 برای ساخت یک پروژه جدید با استفاده از دستورهای Vue CLI طبق مراحل زیر کار را انجام می دهیم:

ابتدا دستورهای زیر را به ترتیب در ترمینال برنامه ویرایشگر کد موردنظر خود وارد کنید.

npm install -g @vue/cli
vue create my-project

پس از ساخت پروژه ی موردنظر بهتر است در همین ابتدای کار، Vue Router را نیز استفاده کنید تا بتوانید بین صفحه های مختلف پروژه ی خود جا به جا شوید. برای این کار کافی است از دستور زیر استفاده کنید:

vue add router

البته این نکته را هم در نظر بگیرید که در حین اجرای دستور vue create my-project سوالاتی از شما پرسیده می شود و می توانید در همان جا router را انتخاب کنید که نصب شود و دیگر نیازی به اجرای دستور vue add router نیست.پکیج دیگری که باید نصب کنید axios است. از axios برای ارسال درخواست ها و دریافت پاسخ آنها از سمت سرور استفاده می شود. به عبارتی از axios برای ارتباط برقرار کردن با سرور استفاده می شود و با دستور های زیر می توان آن را نصب کرد:

npm install --save axios vue-axios

مرحله ی 2: پیاده سازی صفحه های موردنظر

 پس از آن که پروژه خود را ساختید باید کامپوننت های زیر مجموعه ی آن و همچنین صفحه هایی را که قصد دارید در این پروژه استفاده کنید، بسازید. ما در اینجا قصد داریم که چند صفحه شامل صفحه ی خانه، لاگین و پروفایل را ایجاد کنیم.

قبل از ساخت هریک از صفحه ها، بهتر است که نامگذاری route ها (آدرس هر یک از صفحه ها) را انجام دهیم. در قسمت پایین، کد و تصویر صفحه های مختلف از این پروژه را می بینید. 

ابتدا نمودار درختی مربوط به کامپوننت ها و صفحه های موردنظر را در شکل زیر نشان می دهیم و سپس به توضیح هر کدام می پردازیم.

کدهای درون فایل App.vue  به صورت زیر هستند که درون این فایل افزون بر <router-view /> که مربوط به جا به جایی بین صفحه هاست، از دو کامپوننت دیگر به نام های Navigation و Footer که به ترتیب برای نمایش منوی بالای صفحه و قسمت پایین صفحه هستند، نیز استفاده شده است.



<template>

  <div id="app">

    <Navigation />

    <div class="main-container">

      <router-view />

    </div>

    <Footer />

  </div>

</template>



<script>

import Navigation from "./components/Navigation";

import Footer from "./components/Footer";



export default {

  name: "App",

  components: {

    Navigation,

    Footer,

  },

};

</script>

کامپوننت Navigation به صورت زیر نوشته می شود. همانطور که مشاهده می کنید  این کامپوننت شامل سه router-link است که با کلیک کردن روی هر یک از آن ها می توان به صفحه مورد نظر منتقل شده و به اطلاعات درون آن دسترسی داشت.

<template>

  <div class="navigation" dir="rtl">

    <ul>

      <li>

        <router-link class="brand" to="/">

          <img src="../assets/avatar/avatar.png" width="40px" class="avatar" />

          <router-link to="/profile">

            <strong>سکان آکادمی</strong>

          </router-link>

        </router-link>

      </li>

    </ul>

    <ul>

      <li>

        <router-link to="/home">خانه</router-link>

      </li>

      <li>

        <router-link to="/login">ورود</router-link>

      </li>

    </ul>

  </div>

</template>



<script>

export default {

  name: "navigation",

};

</script>

کد مربوط به کامپوننت Footer نیز به صورت زیر است:

<template>

  <div class="sq-footer">

    <a href="https://sokanacademy.com/">Sokan Academy</a>

  </div>

</template>



<script>

export default {

  name: "sqreen-footer",

};

</script>

تا اینجای کار، خروجی به صورت زیر است:

از آنجا که با کلیک کردن روی هر یک از لینک های موجود در منوی بالای صفحه، باید اطلاعات مربوط به آن نمایش داده شود بنابراین به بررسی فایل index.js از پوشه ی router می پردازیم. کدی که باید در این فایل جاوااسکریپتی بنویسیم به صورت زیر است:

import Vue from 'vue'

import VueRouter from 'vue-router'



Vue.use(VueRouter)



const routes = [

  {

    path: '/home',

    name: 'Home',

    component: ()=> import('../views/Home')

  },

  {

    path: '/profile',

    name: 'Profile',

    component: ()=> import('../views/Profile')

  },

  {

    path: '/login',

    name: 'LoginPage',

    component: ()=> import('../views/LoginPage')

  },

]



const router = new VueRouter({

  mode: 'history',

  base: process.env.BASE_URL,

  routes

})



export default router

در اینجا فقط کد مربوط به صفحه لاگین را نشان می دهیم و برای صفحه های دیگر خودتان به اختیار می توانید هر چیزی بنویسید. برای ایجاد صفحه ی لاگین از یک فرم ساده مانند کد زیر، برای وارد کردن اطلاعات مربوط به لاگین کردن کاربر استفاده کردیم. 

<template>

  <div class="LoginPage">

    <form class="login" @submit.prevent="login" dir="rtl">

      <h1>ورود</h1>

      <label>نام کاربری: </label>

      <input required v-model="username" type="text" placeholder="نام کاربری" />

      <br />

      <br />

      <label>رمز عبور: </label>

      <input required v-model="password" type="password" placeholder="رمز عبور" />

      <br />

      <br />



      <button type="submit">ورود</button>

    </form>

  </div>

</template>

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

زمانی که کاربر رمز و نام کاربری خود را وارد می کند با زدن دکمه ی “ورود”، انتظار دارد که به صفحه ی مورد نظر منتقل شود اما با توجه به اینکه این دکمه هنوز کار نمی کند، بهتر است ابتدا ما یک متد برای آن تعریف کنیم که با صدا زدن آن، کاربر به صفحه ی موردنظر انتقال یابد.

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

    methods: {

    login() {

      this.$router.replace("/home");

    },

  },

مرحله ی 3: ایجاد یک فایل جاوااسکریپتی برای انجام عملیات مختلف روی token ذخیره شده

اعمال دسترسی به صفحه ها به درست بودن یا نبودن token مورد استفاده، بستگی دارد اما برای بررسی درست بودن یا نبودن token بهتر است یک فایل جاوااسکریپتی جداگانه را ایجاد کرده و این موضوع درون آن بررسی شود. ما در اینجا یک فایل به اسم AuthService.js را درون پوشه ای جدید به اسم services (در پوشه ی src) قرار دادیم. کد نوشته شده در آن به صورت زیر است:

import axios from "axios";



export const setAuthToken = (token) => {

  if (token) {

    localStorage.setItem("token", token);

    axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;

  } else {

    localStorage.removeItem("token");

    delete axios.defaults.headers.common["Authorization"];

  }

};



export const checkAuth = () => {

  if (localStorage.getItem("token")) {

    return true;

  } else {

    return false;

  }

};

در این فایل از دو تابع با اسم های setAuthToken و checkAuth استفاده شده است. همانطور که در تابع اول مشاهده می شود یک آرگومان ورودی به اسم token وجود دارد که همان توکنی است که پس از لاگین کردن کاربر، از سمت سرور ارسال می شود. درون این تابع از یک شرط استفاده شده است که در صورت پر بودن توکن (وقتی که کاربر لاگین کرده)، آن را در localStorage مرورگر ذخیره کرده و در صورت خالی بودن توکن (وقتی کاریر خارج شده)، آن را از localStorage حذف می کند.

دقت شود که در کد زیر برای headers تمام درخواست های ارسال شده به سرور (برای کاربر لاگین شده) از کلمه ی Authorization و در مقابل آن از Bearer استفاده شده است. 

axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;

تابع دوم از این فایل برای بررسی وجود توکن در localStorage مرورگر نوشته شده است و در صورتی که کلید توکن در localStorage وجود داشته باشد true‌ و در غیر این صورت false بر می گرداند. بنابراین برای اعمال دسترسی به route ها باید از این تابع استفاده کرد، به این معنی که در صورت وجود دسترسی، مقدار true بر می گرداند و در غیر این صورت false خواهد شد.

مرحله ی4: ذخیره کردن token گرفته شده از سرور

 در این مرحله باید token گرفته شده از سمت سرور هنگام لاگین کردن، در محلی ذخیره شود تا برای اعمال دسترسی به صفحه های مختلف از آن استفاده کرد. دقت شود که این token وقتی از سمت سرور برای کاربر ارسال می شود که اطلاعات کاربر (نام کاربری و رمز عبور) در صفحه ی لاگین به درستی وارد شود و اگر اطلاعات وارد شده برای کاربر اشتباه باشد، سرور به جای ارسال token خطای 401 (البته بستگی به پیاده سازی توسعه دهنده سمت سرور دارد زیرا ممکن است که او به جای خطای 401 از 403 استفاده کند ولی در این پروژه فرض کردیم که سرور خطای 401 می دهد) می دهد. برای ذخیره ی توکن در مرورگر، کافی است کد زیر را به متد login که قبلاً تعریف شد اضافه کنیم. در کد زیر با ارسال اطلاعات کاربر به سمت سرور، در صورت درست بودن اطلاعات، توکن مخصوص کاربر موردنظر را در پاسخ درخواست ارسال می کند که برای ذخیره ی آن کافی است یک بار تابع setAuthToken را با آرگومان ورودی به دست آمده از سرور صدا بزنیم. در این صورت توکن ذخیره می شود و پس از ذخیره، کاربر به صفحه با آدرس “/home” منتقل می شود. 

توجه: دقت کنید که در کدهای زیر منظور از "API"، در واقع همان آدرس API ی است که قرار از آن استفاده کنید.

             const body = {

        username: this.username,

        password: this.password,

      };



      axios

        .post("API", body)

        .then((resp) => {

          const token = resp.data.token;

          setAuthToken(token);

          this.$router.replace("/home");

        })

        .catch((error) => {

          console.log(error);

        });

مرحله ی 5: اعمال دسترسی ها به صفحه های مختلف متناسب با نیاز کاربر

 مرحله ی پنجم از این کار مربوط به اعمال دسترسی های صفحه ها، برای کاربران است که در فایل index.js انجام می شود. با توجه به تابع checkAuth که قبلاْ تعریف شد، کافی است در اینجا دو تابع جدید تعریف شود که مربوط به دو حالت کاربران لاگین شده و کاربران لاگین نشده است. توابع تعریف شده به صورت زیر هستند:

const isGuest = (to, from, next) => {

  const userisAuthenticated = checkAuth();



  if (userisAuthenticated) {

    next('/home');

    return;

  }



  next();

};



const isAuthenticated = (to, from, next) => {

  const userisAuthenticated = checkAuth();



  if (!userisAuthenticated) {

    next('/login');

    return;

  }



  next();

};

همان طور که مشاهده می شود توابع با اسم های isGuest و isAuthenticated استفاده شد. تابع isGuest برای صفحه های عمومی استفاده می شود که نیازی به لاگین کردن ندارند مانند صفحه ی وارد کردن اطلاعات کاربر، اما تابع isAuthenticated برای صفحه هایی استفاده می شود که کاربر باید حتماً لاگین کرده باشد مانند صفحه ی مربوط به پروفایل کاربر. توابع isGuest و isAuthenticated در واقع میان افزار (middleware) هستند. این توابع به نوعی رابط بین صفحات مختلف هستند به عبارتی، زمانی که قصد دارید از یک صفحه به صفحه منتقل شوید در میانه راه، با استفاده از این توابع می توان اجازه حرکت به سمت صفحه بعد را دریافت کرد. برای مثال تابع isAuthenticated برای بررسی وضعیت لاگین بودن کاربر استفاده می شود به گونه ای که در حین جا به جا شدن بین دو صفحه، بررسی می کند که اگر وضعیت کاربر از حالت لاگین خارج شده باشد (در اثر پاک شدن و یا فرا رسیدن تاریخ انقضای توکن)، او را به صفحه لاگین به آدرس "/login" منتقل می کند.

برای مطالعه ی بیشتر در مورد to، from و next که در توابع بالا به کار برده شد، اینجا کلیک کنید.

اما چطور باید از توابع isGuest و isAuthenticated استفاده کرد و دسترسی ها را اعمال کرد؟

برای انجام این کار کافی است که به هر یک از صفحه ها، تابع مورد نیاز را اضافه کرد. دقت کنید که به صورت کلی، هر صفحه می تواند یکی از سه حالت زیر را داشته باشد که متناسب با نیاز پروژه می توان برای آن دسترسی اعمال کرد:

  1. صفحه،  در دسترس تمام کاربران (لاگین شده و لاگین نشده) باشد، که به آن صفحه ی عمومی گفته می شود.
  2. صفحه، فقط در دسترس کاربران لاگین شده باشد، که به آن صفحه ی خصوصی گفته می شود.
  3. صفحه، فقط در دسترس کاربران لاگین نشده باشد.

پروژه ی ما تنها سه صفحه دارد که دو تا از صفحه های آن به صورت خصوصی هستند و یکی از آن ها که در واقع صفحه ی وارد کردن اطلاعات کاربری است برای کاربران لاگین نشده است. بنابراین می توان مانند کد زیر عمل کرد: 

const routes = [

  {

    path: '/home',

    name: 'Home',

    component: ()=> import('../views/Home'),

    beforeEnter: isAuthenticated,

  },

  {

    path: '/profile',

    name: 'Profile',

    component: ()=> import('../views/Profile'),

    beforeEnter: isAuthenticated,

  },

  {

    path: '/login',

    name: 'LoginPage',

    component: ()=> import('../views/LoginPage'),

    beforeEnter: isGuest,

  },

]

در کدهای بالا beforeEnter یک هوک (hook) vue-router است که در اینجا برای اعمال دسترسی ها به صفحه موردنظر استفاده شده است.

در زیر چند تصویر از حالت های مختلف پروژه ای که با هم ایجاد کردیم، نشان داده شده است. با انجام دادن این تغییرها روی فایل index.js اگر کاربر لاگین نکرده باشد، نمی تواند به صورت مستقیم به صفحه های خانه و پروفایل دسترسی داشته باشد. به عبارتی، اگر آدرس صفحه ی موردنظر (برای مثال  ‘/home’) را در قسمت آدرس بار مرورگر بزند به صفحه ی خانه نخواهد رفت و دوباره به صفحه ی لاگین redirect خواهد شد.

این تصویر مربوط به صفحه خانه است و 

این تصویر اطلاعات پروفایل را نشان می دهد.

نکته: در mounted مربوط به هسته پروژه (که معمولاً فایل App.vue است) حتماً باید از کد زیر استفاده شود:

  mounted() {

    const token = localStorage.getItem("token");

    setAuthToken(token);

  },

مرحله ی 6: تنظیم interceptorها برای ارسال درخواست به سرور

تا اینجای کار پروژه به درستی کار می کند و مشکلی ندارد. اما گاهی اوقات، برای سرور مشکلاتی پیش می آید که نمی توان برای آن درخواست ارسال کرد و در صورت ارسال درخواست، با خطای 401 مواجه می شویم (یعنی سرور کاربر درخواست کننده را نمی شناسد) برای چنین مواقعی بهتر است که ساز و کاری پیاده شود تا پروژه ی ساخته شده با مشکلات بزرگتری روبرو نشود. بهترین کار برای چنین مواقعی، redirect کردن کاربر به صفحه لاگین است به این معنی که دوباره از کاربر خواسته می شود تا احراز هویت انجام دهد. برای این کار کافی است در روت پروژه با استفاده از interceptorها برای خطاهای 401 (البته برای سایر خطاها نیز می توان از این روش استفاده کرد) کاربر را به صفحه ی لاگین redirect کرد. کد زیر را به روت پروژه اضافه کنید.

  created() {

    axios.interceptors.response.use(undefined, (err) => {

      return new Promise((resolve, reject) => {

        if (err.response && err.response.status === 401) {

          const token = null;

          setAuthToken(token);



          this.$router.replace("/login").catch(() => {});

        } 

        throw err;

      });

    });

  },

همانطور که در کد بالا می بینید قبل از redirect شدن به صفحه ی لاگین، توکن ذخیره شده در localStorage را باید حذف کرد زیرا دیگر کاربر لاگینی وجود ندارد که نیاز به توکن داشته باشد. 

جمع بندی

در این مقاله با یکی از روش های احراز هویت برای یک وب سایت آشنا شدید. مراحل احراز هویت به طور کلی به 6 مرحله تقسیم بندی شدند. از مرحله راه اندازی یک پروژه جدید شروع شده و نحوه ذخیره سازی توکن و استفاده از آن و در نهایت اعمال دسترسی ها به صفحات مختلف را در بر می گیرد.

این آموزش هم تمام شد. اما برای درک بیشتر مطالب بیان شده، سعی کنید صفحه های بیشتری به پروژه اضافه کنید و دسترسی های مختلفی به هر یک از آن ها بدهید و مرحله 6 را برای خطاهای با کد 422 نیز انجام دهید تا مفهوم interceptor ها را بهتر درک کنید.

این محتوا آموزنده بود؟
tokenvuejsauthenticationجاوا اسکریپتویو جی استوکناحراز هویتفریمورک

sokan-academy-footer-logo
کلیه حقوق مادی و معنوی این وب‌سایت متعلق به سکان آکادمی می باشد.