/laravel-websockets-sanctum

Laravel Sanctum, Websockets & Vue [API]

Primary LanguagePHP

Laravel Auth

composer require laravel/ui

php artisan ui:auth

Laravel Sanctum

composer require laravel/sanctum

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

php artisan migrate

Add Sanctum's middleware to your api middleware group within your app/Http/Kernel.php file:

use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

To begin issuing tokens for users, your User model should use the HasApiTokens trait:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}
Create a User
php artisan tinker

App\User::create(['name'=>'John Doe', 'email'=>'johndoe@example.org', 'password'=>Hash::make('secret')]);
Issuing API Tokens

In routes/api.php

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('/sanctum/token', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken($request->email)->plainTextToken;
});
Request
POST /api/sanctum/token HTTP/1.1
Host: 127.0.0.1:8000
Accept: application/json
Content-Type: application/json

{
    "email": "johndoe@example.org",
    "password": "secret"
}
Response
27|XHATyNgZAslFhmE3Z3HBXWN86zJH3vaauNTwriBaPFAcroIDDCe1SJ1ysTLoE08ZrpuUgOYp7U5vtLo2

Laravel Websockets

composer require beyondcode/laravel-websockets

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"

php artisan migrate

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

Pusher

composer require pusher/pusher-php-server "~3.0"

.env

BROADCAST_DRIVER=pusher
PUSHER_APP_KEY=s3cr3t
PUSHER_APP_SECRET=s3cr3t
PUSHER_APP_ID=local
PUSHER_APP_CLUSTER=mt1

config/broadcasting.php

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'encrypted' => true,
        'host' => '127.0.0.1',
        'port' => 6001,
        'scheme' => 'http'
    ],
],

config/app.php uncomment

App\Providers\BroadcastServiceProvider::class

Dashboard

http://127.0.0.1:8000/laravel-websockets

Event & Channel

php artisan make:event Chat
class Chat implements ShouldBroadcast{

public $payload = 'Hello World!';
...
public function broadcastOn()
    {
        return new PrivateChannel('App.User.3');
    }
    
public function broadcastAs()
    {
        return 'new-message-event';
    }
...
}

In routes/channels.php

Broadcast::channel('App.User.{id}', function ($user, $id) {
    //Check User's Authorization to listen on the channel.
    return true;
}

Authorizing Private Broadcast Channels

You should place the Broadcast::routes method call within your routes/api.php file:

Broadcast::routes(['middleware' => ['auth:sanctum']]);

Running Laravel & Websocket Server

php artisan serve
php artisan websockets:serve

UI

npm i laravel-echo pusher-js axios
<script>
import Echo from "laravel-echo";
import Pusher from "pusher-js";
import axios from "axios";
axios
  .post("http://127.0.0.1:8000/api/sanctum/token", {
    email: "johndoe@example.org",
    password: "secret",
  })
  .then(({ data }) => {
    let token = data;
    //
    axios({
      method: "GET",
      url: "http://127.0.0.1:8000/api/user",
      headers: {
        Authorization: `Bearer ${token}`,
      },
    }).then(({ data }) => {
      console.log(data);

      let echo = new Echo({
        broadcaster: "pusher",
        key: "s3cr3t",
        wsHost: "127.0.0.1",
        wsPort: 6001,
        forceTLS: false,
        cluster: "mt1",
        disableStats: true,
        authorizer: (channel, options) => {
          console.log(options);
          return {
            authorize: (socketId, callback) => {
              axios({
                method: "POST",
                url: "http://127.0.0.1:8000/api/broadcasting/auth",
                headers: {
                  Authorization: `Bearer ${token}`,
                },
                data: {
                  socket_id: socketId,
                  channel_name: channel.name,
                },
              })
                .then((response) => {
                  callback(false, response.data);
                })
                .catch((error) => {
                  callback(true, error);
                });
            },
          };
        },
      });

      echo.private(`App.User.${data.id}`).listen(".new-message-event", (message) => {
        console.log(message);
      });
    });
  });
</script>

Fire an Event

php artisan tinker
event(new App\Events\Chat())