ساخت احراز هویت تک‌صفحه‌ای با Laravel و Vue.js - (قسمت اول)

Create-a-SPA-With-Role-Based-Authentication

ساخت احراز هویت در Laravel و Vue.js به صورت تک صفحه‌ ای

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

نصب لاراول

برای ساختن چارچوب کلی یک پروژه لاراولی، دستور زیر را در ترمینال اجرا کنید:

laravel new laravel-vue-spa

نکته: جهت استفاده از دستور بالا لازم است پکیج "Laravel-installer" را به صورت سراسری در سیستم خود نصب کرده باشید، در صورتی که قبلا این مورد را انجام نداده اید، از دستور زیر در ترمینال استفاده کنید:

composer global require laravel/installer

ایجاد کاربران و نقش ها (سطوح دسترسی)

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

من برای مدیریت امکان «نقش ها» یک فیلد به نام role را در جدول user اضافه می کنم. شما می توانید از هر نوع ابزار یا پکیج مدیریت نقش جهت ایجاد امکان استفاده از «نقش ها» استفاده کنید.

در این مثال فیلد role برای یک کاربر معمولی مقدار "1" را خواهد داشت و یک کاربر administrator یا مدیر، مقدار "2" را به عنوان مقدار فیلد role می گیرد.

از کد زیر جهت ایجاد یک migration در پروژه لاراول به نام 'create_users_table' استفاده می کنیم:

$table->integer('role')->default(1);

برای آن که تا حد امکان این مثال را ساده کنیم، خاصیت ()nullable را به فیلد "name" تخصیص می دهیم که این کار باعث می شود تا کاربر مجبور نباشد نامی را هنگام ثبت نام کردن برای خود در نظر بگیرد:

$table->string('name')->nullable();

حالا فایل DatabaseSeeder.php را به روز می کنیم تا یک کاربر معمولی و یک کاربر مدیر را ایجاد کنیم، براساس کد زیر:

<?php
use App\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
    public function run()
    {
        User::create([
            'name' => 'Admin',
            'email' => 'admin@test.com',
            'password' => Hash::make('admin'),
            'role' => 2
        ]);
        User::create([
            'name' => 'User',
            'email' => 'user@test.com',
            'password' => Hash::make('secret'),
            'role' => 1
        ]);
    }
}

بعد از انجام تنظیمات دسترسی به دیتابیس در فایل "env."، کد زیر را جهت ساختن جدول "users" با تعریف 2 کاربر، اجرا می کنیم:

php artisan migrate --seed

محافظت از آدرس API با JWT

من در این جا از پکیج tymondesigns/jwt-auth جهت مدیریت احراز هویت API استفاده می کنم. بین نسخه های موجود از این پکیج در این آموزش، من از نسخه 'dev-develop' با دستور زیر استفاده خواهم کرد:

composer require tymon/jwt-auth:dev-develop

حالا باید با استفاده از دستور زیر، تنظیمات JWT را پابلیش کنیم:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

با اجرای کد بالا فایل 'config/jwt.php' ساخته می شود.

سپس با دستور زیر یک JWT secret key ایجاد می کنیم:

php artisan jwt:secret

اجرای دستور بالا موجب تولید یک متغیر از نوع enviroment در فایل 'env.' می گردد.

سپس در فایل 'config/auth.php' محافظت پیش فرض را با تغییر مقدار فیلد guard در آرایه defaults به api، به محافظت api تغییر دهید.

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

بعد مقدار فیلد driver در آرایه api را که خود در آرایه guards قرار گرفته است، به 'jwt' تغییر دهید، مانند کد زیر:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

اکنون مدل User را برای پیاده سازی واسط یا اینترفیس 'JWTSubject' به صورت زیر تغییر می دهیم:

<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
    use Notifiable;
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];
    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
    protected $casts = [
‘email_verified_at’ => ‘datetime’,
];
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }
    
    public function getJWTCustomClaims()
    {
        return [];
    }
}

نکته 1: در لاراول نسخه 6، فیلد casts$ در فایل User.php اضافه شده است.

نکته 2: توجه کنید که متدهای ()getJWTIdentifier و ()getJWTCustomClaims را فراموش نکنید، زیرا اینترفیس یا واسط به آن نیاز دارد.

مسیر یا روت 'api/user' که در فایل 'routes/api.php' موجود است، از 'auth:api' به عنوان middleware استفاده می کند که حالا با jwt پیکربندی و تنظیم شده است.

اگر در مرورگر آدرس http://127.0.0.1:8000/api/user را وارد کنید، دسترسی به این مسیر پذیرفته نخواهد شد و با یک صفحه خطا که شامل پیام 'Route [login] not defined.' است، رو به رو خواهید شد.

توجه: جهت دسترسی به نمایش پروژه با استفاده از دستور 'php artisan serve'، سرور داخلی لاراول را راه اندازی کنید. آدرس پیش فرض جهت نمایش پروژه، 'http://127.0.0.1:8000' می باشد.

عملکرد مربوط به 'auth:api' که در بالا به عنوان middleware مطرح شد، در 'app/Http/Middleware/Authenticate.php' تعریف شده است. همان طور که اشاره شد، یک api ساخته می شود تا پاسخ هایی را به فرمت json ارسال کند و یک صفحه ورود (login) که با vue مدیریت می شود. پس این امکان وجود دارد تا برای ارسال خروجی به فرمت json تغییرات زیر را اعمال کنیم (انتخابی هستند):

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
    public function handle($request, Closure $next, ...$guards)
    {
        if ($this->authenticate($request, $guards) === 'authentication_error') {
            return response()->json(['error'=>'Unauthorized']);
        }
        return $next($request);
    }
    protected function authenticate($request, array $guards)
    {
        if (empty($guards)) {
            $guards = [null];
        }
        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }
        return 'authentication_error';
    }
}

تست api

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

بخش پایانی پیاده سازی احراز هویت

حالا آخرین مرحله پیاده سازی احراز هویت را در کدهای سرور لاراول انجام می دهیم.

در فایل 'routes/api.php' کدهای زیر را اضافه کنید:

Route::prefix('auth')->group(function () {
    Route::post('register', 'AuthController@register');
    Route::post('login', 'AuthController@login');
    Route::get('refresh', 'AuthController@refresh');
    Route::group(['middleware' => 'auth:api'], function(){
        Route::get('user', 'AuthController@user');
        Route::post('logout', 'AuthController@logout');
    });
});
  • مسیر 'api/auth/register' جهت ایجاد کاربران استفاده می شود.
  • مسیر 'api/auth/login' جهت پیاده سازی امکان ورود (login) استفاده می شود.
  • مسیر 'api/auth/refresh' جهت آپدیت شدن مقدار توکن تخصیص داده شده به کاربر استفاده می شود.

هر 3 مسیر بالا در حالت public و برای همه قابل استفاده است و نیازی به احراز هویت ندارد.

  • مسیر 'api/auth/user' جهت واکشی اطلاعات کاربر استفاده می شود.
  • مسیر 'api/auth/logout' جهت خروج کاربر استفاده می شود.

هر 2 مسیر بالا فقط برای کاربران احراز هویت شده قابل دسترسی اند.

حالا در پروژه لاراولی خود به کنترلری نیاز داریم تا این درخواست ها را مدیریت کند، پس داریم:

php artisan make:controller AuthController

دستور بالا موجب ساخت فایل 'app/Http/Controllers/AuthController.php' می شود.

متدهای مورد نیاز را همان طور که در پایین مشاهده می کنید، به آن اضافه کنید:

<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class AuthController extends Controller
{
    public function register(Request $request)
    {
        $v = Validator::make($request->all(), [
            'email' => 'required|email|unique:users',
            'password'  => 'required|min:3|confirmed',
        ]);
        if ($v->fails())
        {
            return response()->json([
                'status' => 'error',
                'errors' => $v->errors()
            ], 422);
        }
        $user = new User;
        $user->email = $request->email;
        $user->password = bcrypt($request->password);
        $user->save();
        return response()->json(['status' => 'success'], 200);
    }
    public function login(Request $request)
    {
        $credentials = $request->only('email', 'password');
        if ($token = $this->guard()->attempt($credentials)) {
            return response()->json(['status' => 'success'], 200)->header('Authorization', $token);
        }
        return response()->json(['error' => 'login_error'], 401);
    }
    public function logout()
    {
        $this->guard()->logout();
        return response()->json([
            'status' => 'success',
            'msg' => 'Logged out Successfully.'
        ], 200);
    }
    public function user(Request $request)
    {
        $user = User::find(Auth::user()->id);
        return response()->json([
            'status' => 'success',
            'data' => $user
        ]);
    }
    public function refresh()
    {
        if ($token = $this->guard()->refresh()) {
            return response()
                ->json(['status' => 'successs'], 200)
                ->header('Authorization', $token);
        }
        return response()->json(['error' => 'refresh_token_error'], 401);
    }
    private function guard()
    {
        return Auth::guard();
    }
}

توجه: من جهت ساده سازی کار در این پروژه اعتبارسنجی های کمتری را انجام دادم. به عنوان مثال یک اعتبارسنجی در متد '()register' انجام دادم اما در شرایط واقعی این کار برای متد '()login' نیز انجام می پذیرد و یا از بلوک کدهای try/catch جهت مدیریت خطاها مانند خطاهای سرور و دیتابیس و... در حالت واقعی استفاده می کنیم.

متد ()register بسیار ساده پیاده سازی شده است و صرفا کاربری را ایجاد می کند و در پایان یک سری فیلد بر می گرداند و قبل از آن اقدام به هش کردن پسورد کاربر می کند.

متد ()login از متد Auth:guard به عنوان middleware استفاده می کند که در آن هم از jwt استفاده شده است. و در ساختار متد ()login، متد ()attempt اعتبارسنجی های لازم را جهت تولید توکن کاربر که در هدر پاسخ برگشتی از متد ()login قرار می گیرد (در صورت اعتبارسنجی موفق)، تولید می کند.

متد '()logout' جهت خروج کاربران و غیرفعال سازی توکن کابران در نظر گرفته می شود.

متد '()user' جهت واکشی اطلاعات کاربر کنونی که در سیستم وارد شده است، در نظر گرفته می شود.

متد 'refresh' جهت آپدیت کردن مقدار توکنی است که منقضی شده است. این امکان وجود دارد تا طول اعتبار یک توکن را در فایل 'config / jwt.php' تعیین کنید.

حالا با ابزارهای تستی مانند postman می توانید مراحل پایانی سیستم احراز هویت را از ثبت نام کاربران تا خروج کاربران تست کنید. در زیر تصاویر تست api های مختلف را در postman مشاهده می کنید:

postman postman postman postman postman

تکمیل محافظت از سیستم احراز هویت با استفاده از نقش ها

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

حالا با دستورات زیر (در ترمینال)، middleware ها را ایجاد می کنیم:

php artisan make:middleware CheckIsAdmin
php artisan make:middleware CheckIsAdminOrSelf

دستورات بالا دو فایل در دایرکتوری 'app/http/Middleware' ایجاد می کنند.

در اولین middleware (فایل CheckIsAdmin.php) یک بررسی ساده روی این موضوع که آیا کاربرِ وارد شده نقش مدیر دارد یا خیر، انجام می دهیم. بدین منظور به جای کدهای موجود در این فایل، کدهای زیر را قرار دهید:

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class CheckIsAdmin
{
    public function handle($request, Closure $next)
    {
        if(Auth::user()->role === 2) {
            return $next($request);
        }
        else {
            return response()->json(['error' => 'Unauthorized'], 403);
        }
    }
}

در middleware دیگر (فایل CheckIsAdminOrSelf.php) دو حالت بررسی می شوند که شباهت هایی با middleware قبلی دارد، اما الگوی استفاده و کاربرد آن متفاوت از کاربرد middleware قبلی است. در این middleware این موضوع بررسی می شود که آیا کاربر وارد شده یک ادمین است که قصد مشاهده اطلاعات خود یا کاربر دیگری را دارد و یا یک کاربر معمولی است که فقط قصد مشاهده اطلاعات مربوط به خود را دارد؟

حالا middleware ها را در فایل 'app/Http/kernel.php' تعریف می کنیم. خطوط زیر را به آرایه 'routeMiddleware$' اضافه می کنیم:

'isAdmin' => \App\Http\Middleware\CheckIsAdmin::class,
'isAdminOrSelf' => \App\Http\Middleware\CheckIsAdminOrSelf::class,

هم اکنون مسیر های لازم جهت دسترسی به اطلاعات کاربری را ایجاد می کنیم و از آن ها با middleware های مناسب محافظت می کنیم:

Route::group(['middleware' => 'auth:api'], function(){
    // Users
    Route::get('users', 'UserController@index')->middleware('isAdmin');
    Route::get('users/{id}', 'UserController@show')->middleware('isAdminOrSelf');
});

در نهایت کنترلری را برای کاربران ایجاد و متدهای '()index' و '()show' را در آن تعریف می کنیم:

php artisan make:controller UserController

دستور بالا فایل زیر را تولید می کند که محتویاتش به صورت زیر است:

<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
    public function index()
    {
        $users = User::all();
        return response()->json(
            [
                'status' => 'success',
                'users' => $users->toArray()
            ], 200);
    }
    public function show(Request $request, $id)
    {
        $user = User::find($id);
        return response()->json(
            [
                'status' => 'success',
                'user' => $user->toArray()
            ], 200);
    }
}

با این رویه در نهایت مسیر 'api/users' تنها برای ادمین قابل دسترسی است و مسیر 'api/users/2' فقط برای ادمین ها و کاربری که id ای او برابر 2 باشد، قابل دسترسی است. تصویر زیر را مشاهده کنید:

postman

همه موارد مرتبط با قسمت backend احراز هویت در لاراول به پایان رسید، در قسمت بعد با تکمیل frontend به کمک vueJs کل پروژه احراز هویت تکمیل می شود.


منبع: سایت Medium

نویسنده شوید

دیدگاه‌های شما (2 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

امیر
01 مهر 1399
عالی

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

محمد
23 خرداد 1399
سلام بنده باهاش کار کردم خوب بود فقط کد یکی از میدلویرها رو یادت رفته بزاری از سایت اصلی برداشتم

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.