Xác thực API sử dụng Laravel passport

118

Laravel là PHP framework được rất nhiều các developer sử dụng trong các dự án web bởi tính vô cùng tiện lợi, gọn gàng, nhanh chóng, lại đi kèm một hệ sinh thái đầy đủ và mạnh mẽ. Không chỉ dừng lại ở một MVC framework đơn thuần, Laravel còn được sử dụng để xây dựng các dự án web service theo chuẩn RESTful vô cùng nhanh chóng.

Một trong những thao tác quan trọng khi xây dựng web service theo chuẩn RESTful chính là cơ chế xác thực API. Biết được điều này nên Laravel đã cho ra đời gói Laravel passport giúp việc xác thực API trở nên đơn giản hơn.

Tuy nhiên mình thấy phần tài liệu của Laravel passport hơi rườm rà khó hiểu, nên mình viết bài viết này nhằm chia sẻ tới các bạn cách tích hợp gói Laravel passport vào dự án Laravel như cách mình vẫn thường làm. Để bạn nào chưa rõ có thể tham khảo, bạn nào biết rồi có thể cho mình góp ý nhé.

Bài viết này mình sẽ sử dụng Laravel 5.5, tuy nhiên với các phiên bản Laravel 5.x khác cũng có cách tích hợp tương tự.

I. Cài đặt project Laravel

1.1 Kiểm tra PHP version

Laravel 5.5 yêu cầu phiên bản tối thiểu của là php 7.0.0. Nên trước khi cài đặt, chúng ta hãy kiểm tra phiên bản PHP trước bằng cách sử dụng command

php -v

Như trên máy của mình, thì sẽ có kết quả là

PHP 7.0.33-0ubuntu0.16.04.6 (cli) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.0.33-0ubuntu0.16.04.6, Copyright (c) 1999-2017, by Zend Technologies

Vậy là mình đang sử PHP 7.0.33, đủ yêu cầu của Laravel 5.5.

Bạn nào dùng phiên bản PHP nhỏ hơn có thể có cân nhắc giữa việc nâng cấp PHP hoặc sử dụng phiên bản laravel phiên bản thấp hơn nhé

1.2 Cài đặt Laravel 5.5 và tích hợp gói Laravel passport

Cài đặt dự án Laravel bằng cách sử dụng Composer

composer create-project --prefer-dist laravel/laravel blog "5.5.*"

Tích hợp Laravel passport vào dự án Laravel

cd blog
composer require paragonie/random_compat:2.*
composer require laravel/passport "4.0.*"

Publish các migrations của Laravel passport

php artisan vendor:publish --tag=passport-migration

Tạo database và cấu hình thông tin kết nối trong file .env

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=laravel_blog
DB_USERNAME=root
DB_PASSWORD=root

Chạy migrate để tạo các tables cần thiết trong database

php artisan migrate

II. Cấu hình Laravel passport

Tạo một số keys cần thiết cho Laravel passport

php artisan passport:install

Thêm trait Laravel\Passport\HasApiTokens cho model App\User

<?php
// app/User.php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use Notifiable, HasApiTokens;
    //...
}

Mở class App\Providers\AuthServiceProvider, thêm Passport::routes() vào method boot()

<?php
// app/Providers/AuthServiceProvider.php

namespace App\Providers;

use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    //...
    public function boot()
    {
        //...
        Passport::routes();
        //...
    }

    //...
}

Chỉnh driver cho api guard từ token thành passport trong config/auth.php

// config/auth.php
'guards' => [
     'web' => [
         'driver' => 'session',
         'provider' => 'users',
     ],
     'api' => [
         'driver' => 'passport',
         'provider' => 'users',
     ],
 ],
//...

III. Tạo các endpoint cần thiết

Chúng ta sẽ lần lượt tạo các endpoint được tóm tắt trong bảng sau

methodURLMiddlewareMô tả
POST/api/auth/loginĐăng nhập
POST/api/auth/signupĐăng ký tài khoản
DELETE/api/auth/logoutapi:authĐăng xuất
GET/api/auth/meapi:authLấy thông tin user đang login

Mở file routes/api.php, xóa hết nội dung và thay thế bằng

<?php
// routes/api.php

use Illuminate\Http\Request;

Route::group([
    'prefix' => 'auth'
], function () {
    Route::post('login', '[email protected]');
    Route::post('signup', '[email protected]');
  
    Route::group([
      'middleware' => 'auth:api'
    ], function() {
        Route::delete('logout', '[email protected]');
        Route::get('me', '[email protected]');
    });
});

Tạo AuthController bằng command sau

php artisan make:controller AuthController

Mở App\Http\Controllers\AuthController vừa tạo, xóa hết nội dung và thay thế bằng

<?php
// app/Http/Controllers/AuthController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\User;
use Validator;

class AuthController extends Controller
{
    public function signup(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required|string',
            'email' => 'required|string|email|unique:users',
            'password' => 'required|string|confirmed'
        ]);

        if ($validator->fails()) {
            return response()->json([
                'status' => 'fails',
                'message' => $validator->errors()->first(),
                'errors' => $validator->errors()->toArray(),
            ]);
        }

        $user = new User([
            'name' => $request->name,
            'email' => $request->email,
            'password' => bcrypt($request->password)
        ]);

        $user->save();

        return response()->json([
            'status' => 'success',
        ]);
    }

    public function login(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'email' => 'required|string|email',
            'password' => 'required|string',
            'remember_me' => 'boolean'
        ]);

        if ($validator->fails()) {
            return response()->json([
                'status' => 'fails',
                'message' => $validator->errors()->first(),
                'errors' => $validator->errors()->toArray(),
            ]);
        }

        $credentials = request(['email', 'password']);

        if (!Auth::attempt($credentials)) {
            return response()->json([
                'status' => 'fails',
                'message' => 'Unauthorized'
            ], 401);
        }

        $user = $request->user();
        $tokenResult = $user->createToken('Personal Access Token');
        $token = $tokenResult->token;

        if ($request->remember_me) {
            $token->expires_at = Carbon::now()->addWeeks(1);
        }

        $token->save();

        return response()->json([
            'status' => 'success',
            'access_token' => $tokenResult->accessToken,
            'token_type' => 'Bearer',
            'expires_at' => Carbon::parse(
                $tokenResult->token->expires_at
            )->toDateTimeString()
        ]);
    }

    public function logout(Request $request)
    {
        $request->user()->token()->revoke();
        return response()->json([
            'status' => 'success',
        ]);
    }

    public function user(Request $request)
    {
        return response()->json($request->user());
    }
}

IV. Thử nghiệm các api

Mình sẽ sử dụng Postman để thử các endpoint đã tạo ra ở phần trên.

Trong ảnh chụp dưới đây, mình deploy Laravel trên local với domain là http://laravel-blog.local, bạn nào không muốn deploy có thể chạy command sau

php artisan serve

Laravel server sẽ truy cập được ở http://localhost:8000 sau khi chạy command trên.

Bạn có thể bấm vào ảnh để xem rõ hơn nhé.

4.1 Endpoint đăng ký

Endpoint này bạn sẽ phải gửi các thông tin của tài khoản, bao gồm:

  • name: Họ và tên
  • email: Địa chỉ email sử dụng để đăng nhập
  • password: Mật khẩu sử dụng để đăng nhập
  • password_confirmation: Xác nhận mật khẩu

4.2 Endpoint đăng nhập

Trường emailpassword chính là hai thông tin mà bạn đã đăng ký ở endpoint trên.

Trong phần response của api này, có hai thông tin bạn cần chú ý là

  • access_token: Đây là token sử dụng để xác thực tài khoản, bạn sẽ phải truyền token này trong header đối với các endpoint đi qua middleware api:auth
  • token_type: Đây là loại token, mặc định nó sẽ là Bearer, bạn cũng phải truyền thông tin này trong header đối với các endpoint đi qua middleware api:auth

4.3 Endpoint lấy thông tin user đang login

Trong endpoint này, bạn hãy chú ý tới phần Header, mình có truyền lên hai thông tin quan trọng là

  • Authorization: Là thông tin để xác thực tài khoản, nó chính là token_typeaccess_token.
  • X-Requested-With: Có giá trị là XMLHttpRequest. Dựa vào thông này Laravel sẽ biết được là bạn đang gửi một XMLHttpRequest và có cách response phù hợp hơn.

Bất kỳ endpoint nào đi qua middleware api:auth đều phải truyền 2 thông tin Header như trên

4.4 Endpoint đăng xuất

Nếu thực hiện thành công api này, thì access_token sẽ bị vô hiệu hóa.

V. Kết luận

Bài viết chủ yếu là code nên các bạn phải đọc kỹ và copy cẩn thận. Để các bạn tiện theo dõi hơn thì toàn bộ source của bài này được mình để trên github tại repo laravel-api-authentication, các bạn có thể tham khảo thêm nếu muốn.

Bài viết được viết dựa trên kinh nghiệm cá nhân, xin nhận mọi gạch đá góp của các bạn.


(*) Request: Là các thông tin được gửi từ client tới server.

(*) Response: Là các thông tin và server trả về client.

(*) Header: Trong phạm vi bài viết này, Header chính là HTTP Header. Nó là một phần của request, hoặc response. Trong header thường chứa các thông tin mô tả về client (trong trường hợp gửi request), và chưa thông tin của server (trong trường hợp trả về response).