+16

Xác thực ứng dụng chat Realtime với Laravel Passport, SocketIO, Laravel Echo

Hello các bạn lại là mình đây 😎😎

Dạo này dịch bệnh ở VN căng thẳng quá chờ mãi không biết bao giờ con mới được về với đất mẹ 😢😢😢😢 Nhớ giữ gìn sức khoẻ nhé các bạn để có sức còn code 😄

Ở các bài trước trong series viết ứng dụng chat này mình dùng session để xác thực account của user cùng với đó là xác thực cho phía laravel echo. Thế nhưng ở các dự án thật thì việc xác thực qua JWT Token cũng rất phổ biến. Có khá nhiều bạn đã hỏi mình về vấn đề này.

Hôm nay tranh thủ rảnh mình viết luôn bài này để từ nay về sau các bạn có cái để xem trực tiếp và làm theo luôn chứ không cần phải lọ mọ search google nữa 😘

Bài sẽ khá là ngắn chứ không dài dòng văn tự như các bài khác nên các bạn yên tâm nhé 😆😆😆

Setup

Đầu tiên các bạn giúp mình clone source code cho bài này ở đây nhé.

Sau khi clone xong các bạn chạy lần lượt các command sau để cài các thư viện và 1 số setup liên quan nhé:

composer install
npm install
cp .env.example .env

Các bạn update thông tin kết nối database trong .env cho khớp với môi trường của các bạn nhé, sau đó lại tiếp tục chạy các command sau:

php artisan migrate
php artisan passport:keys
php artisan passport:install
php artisan key:generate

Cuối cùng ta thử chạy app lên nhé:

php artisan serve

# mở 1 terminal khác chạy:
npm run watch

Cuối cùng các bạn mở trình duyệt ở địa chỉ http://localhost:8000/chat, click Register để đăng kí tài khoản mới, sau khi đăng kí thành công các bạn sẽ được đưa vào màn hình chính như sau:

Các bạn thử gõ vài tin nhắn và gửi xem mọi thứ có oke hay không nhé, thử F5 lại trình duyệt để đảm bảo tin nhắn cũ được lưu trong database nhé.

Tổng quan project:

  • Ở project này mình dùng Laravel Passport cho authentication (login, register, gửi/load message). Mình dùng JWT Token để xác thực cho tất cả các APIs
  • Các routes cho Laravel mình khai báo ở routes/api.php, phía frontend dùng Vue Router
  • Tất cả các bước setup cho Laravel Passport mình đều follow theo docs của Laravel, không có gì kì diệu ở đây 😄

Âu cây ta bước vào phần chính của ngày hôm nay thôi nào 🚀🚀🚀

Realtime

Điều kiện bắt buộc cho bài này đó là các bạn phải có Redis chạy trên máy nhé, trên windows cài Redis sẽ hơi chuối hơn 1 chút, nên mình khuyến khích các bạn dùng Docker để chạy Redis (1 nốt nhạc là lên 😉)

Đầu tiên ta cài predis cho phía Laravel nhé:

composer require predis/predis

Sau đó ở .env các bạn update lại phần cấu hình broadcast cho Laravel như sau:

...

BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
FILESYSTEM_DRIVER=local
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120

...

Tiếp đến mở file config/database.php tìm mục redis và sửa lại 1 chút ở clientoptions như sau:

'client' => env('REDIS_CLIENT', 'predis'),

'options' => [
    'cluster' => env('REDIS_CLUSTER', 'redis'),
    // comment dòng bên dưới
    //'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],

Tiếp đó mở file config/app.php, tìm mục providers bỏ comment dòng sau nhé:

App\Providers\BroadcastServiceProvider::class

Tiếp theo ta setup Laravel Echo Server nhé. Nếu máy các bạn chưa cài Laravel Echo Server thì các bạn chạy npm install -g laravel-echo-server trước đã nhé

Các bạn cho mình command sau:

laravel-echo-server init

Các bạn lần lượt chọn các option như sau nhé:

Tiếp theo ta mở file laravel-echo-server.json và update trường authEndpoint thêm vào tiền tố /api (vì bài này ta dùng toàn bộ là route /api mà):

"authEndpoint": "/api/broadcasting/auth",

Sau đó ta cần cài Laravel Echo cho phần frontend (VueJS) nhé:

npm i laravel-echo

Các bạn update lại file resources/views/layouts/app.blade.php thêm vào đoạn header ngay sau js/app.js cho mình như sau nhé:

<script src="{{ asset('js/app.js') }}" defer></script>

<!-- thêm vào dòng bên dưới -->
<script src="http://localhost:6001/socket.io/socket.io.js"></script>

Âu cây ta chạy Laravel Echo Server lên nhé:

laravel-echo-server start

Nhớ đảm bảo là laravel echo server chạy ngon nhé các bạn 😃

Tiếp theo ta mở file resources/js/app.js ta update lại 1 chút như sau nhé:

import router from './router/routes'
import Echo from 'laravel-echo' // <<<------- Thêm vào dòng này
...

methods: {
        async getCurrentUser() {
            try {
                .....
            
                window.Echo = new Echo({
                    broadcaster: 'socket.io',
                    host: `${window.location.protocol}//${window.location.hostname}:6001`,
                    auth: {
                        headers: {
                            Authorization: 'Bearer ' + localStorage.getItem('token')
                        }
                    }
                })
            } catch (error) {
                console.log(error)
            }
        }
        
 }

Như các bạn thấy sự khác biệt của bài này so với các bài trước đó là ta chỉ khởi tạo connect tới Laravel echo server sau khi ta lấy được thông tin của user login hiện tại, mục đích để đảm bảo là user đã được xác thực, và đoạn quan trọng là ta truyền token vào trường auth để xác thực với phía Laravel echo server:

auth: {
    headers: {
        Authorization: 'Bearer ' + localStorage.getItem('token')
    }
}

Sau đó ta quay lại trình duyệt bấm F5 để đảm bảo mọi thứ vẫn oke, kiểm tra Network để đảm bảo không có gì có lỗi:

Tiếp theo ta mở file resources/js/components/Chat.vuecreated ta thêm vào như sau:

Echo.private(`chatroom`).listen("MessagePosted", (e) => {
      console.log(e.message);
});

Như ở trên các bạn cũng thấy đó là ta sẽ cho user join vào private channel tên là chatroom và lắng nghe event MessagePosted nếu thấy có tin nhắn mới thì in ra ở console.

Các bạn lưu lại và ta quay lại trình duyệt bấm F5 và để ý terminal nơi đang chạy Laravel Echo Server ta sẽ thấy lỗi in ra như sau:

Lỗi in ra ở đây là vì khi ta join vào channel chatroom thì Laravel Echo Server nó cần phải xác thực với phía Laravel, và vì ta đang đặt authEndpoint (trong laravel-echo-server.json) là /api/broadcasting/auth, mà cái route đó ta lại chưa hề khai báo nó ở đâu cả nên nó báo lỗi không tìm thấy.

Giờ ta mở file routes/api.php và khai báo cho route đó nhé:

...

Route::middleware('auth:api')->post('/broadcasting/auth', function (Request $request) {
    return true;
});

Ổn rồi đó giờ ta quay lại trình duyệt bấm F5 1 lần nữa và quan sát ở terminal nơi đang chạy Laravel Echo Server sẽ thấy như sau nhé:

Vậy là ta đã hoàn thành việc xác thực với Laravel Echo Server bằng JWT Token rồi đó, mọi thứ còn lại thì y như những bài trước.

Đầu tiên ta tạo event MessagePosted (event này sẽ được gọi mỗi khi có tin nhắn được lưu thành công):

php artisan make:event MessagePosted

Sau đó ta mở file app/Events/MessagePosted.php và update 1 số phần như sau:

...

use App\Models\Message; // thêm vào dòng này

class MessagePosted implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;
    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Message $message)
    {
        $this->message = $message;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('chatroom');
    }
}

Cuối cùng ta mở file routes/api.php phần lưu tin nhắn ta thêm vào 1 dòng như sau:

Route::middleware('auth:api')->post('/messages', function (Request $request) {
    $user = Auth::user();

    $message = new App\Models\Message();
    $message->message = request()->get('message', '');
    $message->user_id = $user->id;
    $message->save();

    // Thêm dòng bên dưới
    // Gửi đến các user khác trong phòng TRỪ user tạo tin nhắn này
    broadcast(new App\Events\MessagePosted($message->load('sender')))->toOthers();
    return ['message' => $message->load('user')];
});

Sau đó ta quay lại file Chat.vue và update created và thêm vào method scrollToBottom như sau:

  created() {
    this.loadMessage();

    Echo.private(`chatroom`).listen("MessagePosted", (e) => {
      this.list_messages.push(e.message)
      this.$nextTick(() => {
        this.scrollToBottom();
      });
    });
  },
  methods: {
  ...
    scrollToBottom() {
      const container = document.querySelector(".messages");
      if (container) {
        $(container).animate(
          { scrollTop: container.scrollHeight },
          { duration: "medium", easing: "swing" }
        );
      }
    },
  }

Tiếp theo các bạn nhớ chạy queue:work nhé:

php artisan queue:work

Ổn rồi đó giờ ta quay lại trình duyệt mở 2 tab, login vào 2 tài khoản khác nhau (mở tab ẩn danh nhé), chat thử nếu thấy realtime là ô xờ kê rồi đó:

Xác thực cho từng channel

Nếu các bạn để ý thì bài này ta không cần dùng tới file routes/channels.php, hiện tại thì ta đang return true; tức là cho user join vào bất kì private channel nào, thế nhưng thực tế thường ta sẽ có nhiều channel và với 1 user ta chỉ muốn cho họ join vào 1 vài channel nào đó thôi.

Từ phía Laravel Echo Server thì mỗi lần xác thực nó sẽ gửi kèm theo channel_name và ta có thể dựa vào đó để xử lý tuỳ ý:

use Illuminate\Support\Facades\Log;

...

Route::middleware('auth:api')->post('/broadcasting/auth', function (Request $request) {
    $channel_name = $request->channel_name;
    Log::info($channel_name);
    Log::info(auth()->user());
    
    // Xử lý channel, throw lỗi nếu không muốn cho user join vào channel
    
    return true;
});

Nếu bạn gặp lỗi

Trong khi code nếu có khi nào bạn gặp lỗi thì xem lại bài đầu tiên trong series này của mình phần debug mình đã ghi rấttttttt là tâm huyết rồi các bạn à 😘😘😘

Kết bài

Lâu lắm mới có 1 bài "tàu nhanh" như thế này 😎😎😎.

Qua bài này các bạn thấy rằng việc dùng JWT Token để xác thực với Laravel Echo Server cũng không khó khăn lắm phải không nào? Nhưng vì mình thấy tài liệu và các bài tutorial trên mạng chủ yếu dùng session (như mình làm ở các bài trước), nên khi vào dự án thực tế mà dùng JWT thì bối rối không biết làm như thế nào, thì mong rằng qua bài này các bạn đã hiểu hơn về cách sử dụng nó.

Chúc các bạn thành công và hẹn gặp lại các bạn ở những bài sau


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.