Develoka

Baca Artikel

  •     Lihat Daftar Artikel
  • Realtime Notification dengan Pusher dan Laravel Echo - Part 2

    Memahami lebih lanjut broadcast service pada laravel dengan private channel dan autentifikasi

    by rmdwirizki — Posted on Dec 24, 2017

    Melihat statistik pembaca 2 bulan terakhir, sepertinya artikel bulan lalu yang membahas tentang Realtime Notifications menggunakan laravel dan pusher banyak diminati. Oleh karena itu artikel ini akan membahas kelanjutan tulisan menggantung tersebut #CliffhangerShouldBeIllegal

    Poin Pembahasan

    Sebelum masuk pembahasan teknis, berikut adalah poin - poin yang akan dibahas dalam artikel ini :

    1. Membuat user login sederhana dengan make:auth (silakan skip poin ini jika proses login user sudah dibuat)
    2. Menggunakan multiple channel untuk mengirim pesan ke beberapa user tertentu.
    3. Melakukan autentifikasi terhadap user yang akan menerima pesan melalui private channel dan broadcast route.

    Requirement

    Yang dibutuhkan sebelum melanjutkan tutorial ini adalah hasil project dari artikel sebelumnya, meliputi:

    1. Laravel project
    2. Akun pusher
    3. Package pusher-php-server dari composer
    4. Skema broadcast channel sederhana (dari tutorial sebelumnya)
    5. Session Box pada Ekstensi Chrome (optional)

    Session box digunakan agar kita bisa login dengan akun yang berbeda - beda pada satu browser. Supaya debugging menjadi lebih mudah.

    Skema Login

    Hal pertama yang harus dilakukan adalah membuat skema login, hal ini dilakukan untuk menguji apakah broadcast pesan berhasil diterima oleh user yang berbeda atau tidak. Silahkan lanjut ke poin berikutnya bila skema login sudah ada.

    Make auth

    Buka console atau command line pada root direktori project lalu ketik :

    php artisan make:auth

    Bila berhasil maka akan muncul pesan 'Authentication scaffolding generated successfully'.

    Perintah make:auth akan membuat login.blade.php, register.blade.php, email.blade.php, password.blade.php, home.blade.php beserta file migration dan controller sederhana yang berguna untuk proses login.

    Kemudian jalankan perintah php artisan migrate untuk membuat tabel users di database, hasil dari file migration yang sudah otomatis di-generate oleh make:auth.

    Insert user ke tabel

    Buat fake atau dummy user ke tabel users agar kita bisa login. Bisa insert dari database manager (phpMyAdmin, adminer, dsb), dari halaman register di aplikasi kamu atau lewat seeder-nya laravel.

    Kalau mau coba lewat seeder ikuti langkah berikut:

    php artisan make:seeder UsersTableSeeder

    Buka file [laravel_project]/database/seeds/UsersTableSeeder.php kemudian isi data user. Contoh :

    delete();
    
        User::create([
          'name'     => 'Maulana Unyil',
          'email'    => 'unyil@sarjana.skom',
          'password' => Hash::make('aimanehiraha?'),
        ]);
        User::create([
          'name'     => 'Dadang Legend',
          'email'    => 'dadang@hip.hop',
          'password' => Hash::make('dadang#swag'),
        ]);
      }
    }
    
    

    Kemudian, uncomment baris berisi UsersTableSeeder pada [laravel_project]/database/seeds/DatabaseSeeder.php.

    Uncomment

    Terakhir, jalankan perintah php artisan db:seed.

    Login beberapa user pada aplikasi

    Setelah beberapa user berhasil dibuat, simulasikan agar setiap user tersebut login ke aplikasi. Kita butuh minimal 3 jenis user, yaitu 2 user yang login dan 1 user yang tidak login.

    Ubah halaman welcome.blade.php agar tiap user yang login terlihat kontras bedanya.

    @if(Auth::check()) {{ Auth::user()->name }}! @else Guest @endif

    Sehingga muncul seperti berikut :

    Login multiple user

    Penulis menggunakan chrome extension session box supaya bisa login dengan beberapa user pada satu browser dan browser vivaldi agar bisa melihat banyak tab dalam satu panel seperti gambar di atas.

    Multiple channel broadcast

    Apa itu multiple channel? Ya, artinya banyak channel (badum tss). Memang tidak ada pengertian khusus, hanya bahasa inggris (biar kedengeran keren).

    Di artikel yang lalu kita mengirim pesan melalui satu channel saja yaitu coba-channel, sehingga karena semua user men-subscribe satu channel yang sama, maka semuanya dapat pesan. Padahal ada kasus dimana kita hanya ingin mengirim pesan pada user tertentu saja, misalnya mengirim pesan hanya pada user dengan role admin.

    Ide dasar dari skema ini adalah membuat user men-subscribe nama channel yang berbeda sesuai dengan atribut unik mereka masing-masing, bisa berupa nama role atau bahkan user_id.

    Contoh: Kirim ke user yang user_id-nya 11. Maka nama channel-nya coba-channel.11. Kirim ke user yang role-nya admin, maka semua admin akan men-subscribe channel dengan nama coba-channel.admin dan sebagainya.

    Untuk mempermudah simulasi, atribut unik tiap user yang akan digunakan menjadi nama channel adalah user_id. Jadi pesan akan dikirim secara ke spesifik ke user dengan user_id tertentu.

    Modifikasi class event

    Pada artikel yang lalu, kita sudah membuat event bernama CobaPusherEvent buka file tersebut kemudian modifikasi variabel dan constructor-nya menjadi seperti ini:

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

    Disini kita menambahkan property pada class berupa $userIDs. Property ini adalah array yang akan diisi dengan kumpulan user_id yang akan menerima pesan. Lalu, modifikasi fungsi broadcastOn pada file yang sama menjadi :

    public function broadcastOn()
    {
      $channels = [];
    
      if ($this->userIDs == null) {
        $channels = ['coba-channel'];
      }
      else {
        foreach($this->userIDs as $userID) {
          array_push($channels, 'coba-channel.'.$userID);
        }
      }
    
      return $channels;
    }
    
    

    Jika $userIDs tidak kosong, maka $channels akan berisi array dari nama channel untuk setiap userID yang akan dikirim pesan.

    Contoh:

    Ketika kita mengirim pesan dengan perintah :

    \Event::fire(new \App\Events\CobaPusherEvent('Hello kepada 1 dan 2', [1, 2]))

    Maka $channels akan berisi coba-channel.1 dan coba-channel.2. Jika $userIDs kosong maka $channels hanya akan berisi coba-channel.

    Modifikasi javascript di client

    Sekarang buka halaman welcome.blade.php atau halaman tempat kamu menaruh script dari laravel echo. Modifikasi bagian listen dan subscribe menjadi :

    @if (Auth::check())
      var channel = 'coba-channel.{{ Auth::user()->id }}';
      Echo.channel(channel).listen('CobaPusherEvent', function(e) {
        alert(e.message);
      });
    @else
      var channel = 'coba-channel';
      Echo.channel(channel).listen('CobaPusherEvent', function(e) {
        alert(e.message);
      });
    @endif
    
    

    Coba buka source atau inspect element halaman browser kamu, dan pastikan setiap jenis user men-subcribe nama channel yang berbeda.

    Pastikan nama channel beda untuk tiap user

    Sekarang kita sudah siap untuk mengirim pesan ke banyak channel sekaligus. Tapi sebelumnya, mari kita periksa untuk mengirim pesan hanya pada user yang tidak login dengan cara mengosongkan $userIDs.

    Buka php artisan tinker kemudian ketik perintah :

    \Event::fire(new \App\Events\CobaPusherEvent('Hello'));

    Kirim pesan tanpa userId

    Sekarang coba kirim pesan ke user yang user_id-nya 3 dengan perintah :

    \Event::fire(new \App\Events\CobaPusherEvent('Hello', [3]));

    Kirim pesan ke user yang userId-nya 3

    Kirim ke dua channel sekaligus :

    Kirim pesan ke dua channel sekaligus

    Sederhana bukan? Tapi metode ini memiliki beberapa masalah karena nama channel di tulis secara hardcode di javascript. Mudah saja bagi guest (user yang tidak login) untuk mendapat pesan yang seharusnya dikirim kepada user lain.

    Jika anda menggunakan browser chrome atau turunan chromium lainnya coba hal berikut pada halaman guest : buka inspect element->buka tab sources->buka tab snippets->buat snippet baru->ketikkan blok kode (bagian subscribe untuk nama channel secara manual, misal: coba-channel.4)->ctrl+enter.

    Kemudian kirim lagi pesan untuk user yang user_id-nya 4.

    Intercept message

    Maka dari itu, untuk mengatasi hal tersebut kita perlu memvalidasi user yang akan menerima pesan melalui broadcast route di laravel.

    Broadcast Route

    Garis besarnya, dengan teknik ini setiap halaman yang memiliki script Echo.private saat tereksekusi akan di-validasi secara otomatis oleh laravel echo melalui metode ajax POST ke server laravel. Proses autentifikasi akan dilakukan di route pada bagian broadcast.

    Mengubah channel menjadi private

    Pertama-tama modifikasi dulu file CobaPusherEvent pada fungsi broadcastOn menjadi seperti berikut :

    public function broadcastOn()
    {
      $channels = [];
    
      if ($this->userIDs == null) {
        $channels = ['coba-channel'];
      }
      else {
        foreach($this->userIDs as $userID) {
          // Sebelum
          // array_push($channels, 'coba-channel.'.$userID);
          // Sesudah
          array_push($channels, new PrivateChannel('coba-channel.'.$userID));
        }
      }
    
      return $channels;
    }
    
    

    Kemudian ubah juga pada sisi client menjadi :

    @if (Auth::check())
      var channel = 'coba-channel.{{ Auth::user()->id }}';
      // Ubah dari Echo.channel menjadi Echo.private
      Echo.private(channel).listen('CobaPusherEvent', function(e) {
        alert(e.message);
      });
    @else
      var channel = 'coba-channel';
      Echo.channel(channel).listen('CobaPusherEvent', function(e) {
        alert(e.message);
      });
    @endif
    
    

    Membuat proses autentifikasi di route

    Proses autentifikasi yang akan dilakukan adalah memeriksa apakah session user yang aktif memiliki id yang sama dengan nama channel yang di-subscribe. Buka file routes.php (atau routes/channel.php pada laravel 5.5) lalu salin kode berikut :

    Broadcast::channel('coba-channel.{userId}', function ($user, $userId) {
      // $user diambil dari Auth::user(), artinya session user yang aktif
      // $userId diambil dari nama channel yang di-subscribe
      return $user->id == $userId;
      // mengembalikan nilai true jika id sesuai
    });
    
    

    Tambah csrf_token

    Jika broadcast route belum dibuat atau csrf_token belum ditambahkan yang tampil adalah error seperti berikut :

    Error belum buat route atau csrf_token

    Seperti yang penulis jelaskan di awal, proses autentifikasi melalui proses ajax dengan method POST, oleh karena itu kita butuh menambahkan csrf_token.

    Tambah di head :

    
    
    

    Tambah di script :

    window.Laravel = { 'csrfToken': '{{ csrf_token() }}' };
    
    

    Final Test

    Sekarang kita uji hasil perubahan private channel yang sudah dibuat :

    Final test

    Perhatikan pada tab network di chrome. Autentifikasi yang berhasilkan akan menghasilkan id autentifikasi.

    Sekarang kita coba lagi snippet sebelumnya pada guest :

    Intercept failed