Created
April 22, 2021 15:40
-
-
Save codewithgun/140cfe807cfaf3cfbbeffbaeebf0add5 to your computer and use it in GitHub Desktop.
Laravel web socket with local pusher and custom authentication
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Laravel local websocket | |
| > Customized authentication will be used in this gist instead of default Auth facade provided by Laravel | |
| ## Create project | |
| ``` | |
| composer create-project laravel-laravel your-project-name | |
| cd your-project-name | |
| ``` | |
| ## Install all necessary libraries | |
| ``` | |
| composer require beyondcode/laravel-websockets | |
| composer require pusher/pusher-php-server "~3.0" | |
| ``` | |
| The command above will install `beyondcode/laravel-websockets`, internally used php ratchet to handle web socket connection. Besides that, `pusher/pusher-php-server` is used for replacement of third party **Pusher**. | |
| ## Setup WebSocket and Pusher | |
| After the installation done, run the following command | |
| ``` | |
| php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config" | |
| ``` | |
| The command above will create configuration file for websocket at `config/websockets.php`. Since we are not using **Pusher**, we can put any value for _PUSHER_APP_ID_, _PUSHER_APP_KEY_ and _PUSHER_APP_SECRET_. | |
| ``` | |
| PUSHER_APP_ID=AnyIdYouLike | |
| PUSHER_APP_KEY=AnyKeyYouLike | |
| PUSHER_APP_SECRET=AnySecretYouLike | |
| ``` | |
| Next, we need to use Pusher as your broadcasting driver. It can be done by setting _BROADCASTING_DRIVER_ varaible in your .env | |
| ``` | |
| BROADCAST_DRIVER=pusher | |
| ``` | |
| After that, we are going to change the default behavior of Laravel broadcasting events which is to send the event to official **Pusher** server to our local Pusher API. | |
| Open `config/broadcasting.php` and ensure that the `pusher` value is like below: | |
| ``` | |
| '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, | |
| 'schema' => 'http' | |
| ], | |
| ], | |
| ``` | |
| We are removing `'useTLS' => true` because we are using http instead of https. | |
| > Laravel Pusher replacement able to support multiple WebSocket application with one server. For more information, please visit https://beyondco.de/docs/laravel-websockets/basic-usage/pusher#configuring-websocket-apps | |
| Run `php artisan serve` and `php artisan websocket:serve`. Then, open up your favorite browser such as Firefox and navigate to http://localhost:8000/laravel-websockets. If you get a WebSockets Dashboard, it means you have successfully setup the WebSocket and Pusher. | |
| ## Setup Client | |
| Open up your terminal and run the following command | |
| ``` | |
| npm install --save-dev laravel-echo pusher-js | |
| npm run build | |
| ``` | |
| After that, open `resources/js/bootstrap.js` and add the follwing code | |
| ``` | |
| import Echo from 'laravel-echo'; | |
| window.Pusher = require('pusher-js'); | |
| window.Echo = new Echo({ | |
| broadcaster: 'pusher', | |
| key: process.env.MIX_PUSHER_APP_KEY, | |
| forceTLS: false, | |
| wsHost: window.location.hostname, | |
| wsPort: 6001, | |
| disableStats: true, | |
| }); | |
| ``` | |
| Then, run `npm run dev`. This will use _webpack_ to bundle the code in `resources/js/bootstrap.js` and create bundled js file in `public/js/app.js` | |
| For now, we have finished all the basic setup. Let's start with the code. | |
| ``` | |
| php artisan make:event MessageEvent | |
| ``` | |
| Open your terminal and run the above command to generate an event for broadcasting. | |
| The `MessageEvent.php` file generated at `app/Events`. When the event broadcasted, laravel will automatically convert all the public variable of the class into json. | |
| By default, the generated `MessageEvent` class will broadcast on `PrivateChannel` which require authorization channel. For testing purpose, let change it to `PublicChannel`, which can be connected by anyone publicly. | |
| ``` | |
| public function broadcastOn() | |
| { | |
| return new Channel('channel-name'); | |
| } | |
| ``` | |
| Now, let's add a `message` variable for representing the message to be broadcasted. | |
| ``` | |
| public $message; | |
| public function __construct($message) | |
| { | |
| $this->message = $message; | |
| } | |
| ``` | |
| Then, the `MessageEvent` class must implements `ShouldBroadcast` interface. | |
| ``` | |
| class MessageEvent implements ShouldBroadcast | |
| ``` | |
| Finally, the `MessageEvent.php` will be the same as below. | |
| ``` | |
| class MessageEvent implements ShouldBroadcast | |
| { | |
| use Dispatchable, InteractsWithSockets, SerializesModels; | |
| public $message; | |
| public function __construct($message) | |
| { | |
| $this->message = $message; | |
| } | |
| public function broadcastOn() | |
| { | |
| return new Channel('channel-name'); | |
| } | |
| } | |
| ``` | |
| We have done with the message broadcasting, Let's create an endpoint for client to send message and broadcast to channel `channel-name` | |
| Run the following command to generate a controller. | |
| ``` | |
| php artisan make:controller MessageController | |
| ``` | |
| Add the following code to the `MessageController` newly created. | |
| ``` | |
| class MessageController extends Controller | |
| { | |
| public function send(Request $request) | |
| { | |
| broadcast(new MessageEvent($request->message)); | |
| return response()->json([], 200); | |
| } | |
| } | |
| ``` | |
| The code above create a function, which the request will be routed to `send` function and the sent `message` will be broadcasted to the `channel-name` channel to be received by client. | |
| Now, let's register a route for sending message. | |
| Open `routes/api.php` and add the following route to it. | |
| ``` | |
| Route::post('/send', 'App\Http\Controllers\MessageController@send'); | |
| ``` | |
| We have done all the backend implementation, let's move to client implementation. | |
| Open `resources/js/bootstrap.js` and add the following code to it. | |
| ``` | |
| window.Echo.channel('channel-name').listen('MessageEvent', e => { | |
| console.log(e.message); | |
| }); | |
| ``` | |
| The code above allow client to subscribe to `chat-channel` and listen on message broadcasted by `MessageEvent`. Please make sure that the `listen` function parameter matched with your event class name. | |
| After that, open your terminal and run `npm run dev` to rebundle your javascript file. We have done both client and server implementation. Let's move on to testing. | |
| Since this gist is about creating local websocket without the official `Pusher`, this proof of concept will not any involve user interface development (with console in browser only) and security practices. | |
| Open `welcome.blade.php`, which located at `resources/views` and add the following script to the end of the `<body>` tag. | |
| ``` | |
| <script src='js/app.js'></script> | |
| ``` | |
| The code above is to instruct the browser to load the `app.js` bundled by `npm run dev` to the page. | |
| Now is time for testing. Run the following command in the terminal to start the laravel server and laravel websocket server. | |
| ``` | |
| php artisan serve | |
| php artisan websocket:serve | |
| ``` | |
| Open any 2 browser, and navigate to `localhost:8000`. Once you saw the landing page of the laravel application, press `F12` on your keyboard to open up browser console. Then, copy paste the following code to the console of one of the opened browser. | |
| ``` | |
| fetch("api/send", { | |
| body: JSON.stringify({message: 'Hello from laravel local pusher'}), | |
| headers: { | |
| Accept: "application/json", | |
| "Content-Type": "application/json" | |
| }, | |
| method: "POST" | |
| }) | |
| ``` | |
| Once you execute the code above, you will get `Hello from laravel local pusher` output at another browser. Until now, we have successfully setup local pusher for laravel websocket using public channel. But what about private channel ? | |
| For private channel that use default Auth facade of laravel for authentication, what you need to do is run `php artisan migrate` to create `User` table. After that, change the return type of `broadcastOn` function in `MessageEvent` class to a private channel. | |
| ``` | |
| public function broadcastOn() | |
| { | |
| return new PrivateChannel('channel-name'); | |
| } | |
| ``` | |
| > Please take note that in production environment, there will be multiple private channel instead of hardcoded `channel-name` in this gist. | |
| Then, update the `resources/bootstrap.js` to listen on private channel instead of the public channel. | |
| ``` | |
| window.Echo.private('channel-name').listen('MessageEvent', e => console.log(e)); | |
| ``` | |
| This allow you to have a private channel. User must login from laravel application in order to make the private channel work. | |
| However, what if we didn't wish to use the default Auth facade for channel authentication? | |
| Open `app/providers/BroadcastServiceProvider.php` and update the `boot` function to the following. | |
| ``` | |
| public function boot() | |
| { | |
| Broadcast::routes(['middleware' => ['custom.auth']]); | |
| require base_path('routes/channels.php'); | |
| } | |
| ``` | |
| The code above will allow us to implement custom authentication in our own middleware. Now run the following command to generate the custom authentication middleware. | |
| ``` | |
| php artisan make:middleware CustomAuthentication | |
| ``` | |
| Now, let's register the middleware to the application. Open `app/Http/Kernel.php` and add your middleware into `$routeMiddleware`. | |
| ``` | |
| protected $routeMiddleware = [ | |
| 'auth' => \App\Http\Middleware\Authenticate::class, | |
| 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, | |
| 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, | |
| 'can' => \Illuminate\Auth\Middleware\Authorize::class, | |
| 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, | |
| 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, | |
| 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, | |
| 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, | |
| 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, | |
| 'custom.auth' => \App\Http\Middleware\CustomAuthentication::class | |
| ]; | |
| ``` | |
| Open up `app/Http/Middleware/CustomAuthentication.php` and add the following code. | |
| ``` | |
| public function handle(Request $request, Closure $next) | |
| { | |
| //Mocking authenticated user list without database | |
| $authenticatedUsers = array([ | |
| 'codewithgun' => 'gunwithpassword' | |
| ]); | |
| $authenticatedUsers = array( | |
| 'codewithgun' => 'gunwithcode' | |
| ); | |
| $user = $request->header('user'); | |
| $password = $request->header('password'); | |
| if (!array_key_exists($user, $authenticatedUsers)) { | |
| return response('Unauthorized', 401); | |
| } | |
| if ($authenticatedUsers[$user] != $password) { | |
| return response('Unauthorized', 401); | |
| } | |
| $request->setUserResolver(function () use ($user) { | |
| return $user; | |
| }); | |
| return $next($request); | |
| } | |
| ``` | |
| > The code above just mocking some users data for testing purpose. You should code you custom authentication logic here. | |
| After that, open `routes/channels.php` and add the following channel to it. | |
| ``` | |
| Broadcast::channel('channel-name', function ($user) { | |
| return true; | |
| }); | |
| ``` | |
| The `$user` received here is the authenticated user, which we set into `$request` using `$request->setUserResolver`. If user was not set to `$request`, laravel will block the user. We simply return `true` to authorize the authenticated user to listen on this channel. | |
| Then, open `resources/bootstrap.js` and add some authentication header to laravel echo. | |
| ``` | |
| window.Echo = new Echo({ | |
| broadcaster: 'pusher', | |
| key: process.env.MIX_PUSHER_APP_KEY, | |
| forceTLS: false, | |
| wsHost: window.location.hostname, | |
| wsPort: 6001, | |
| disableStats: true, | |
| auth: { | |
| headers: { | |
| user: 'codewithgun', | |
| password: 'gunwithcode' | |
| } | |
| } | |
| }); | |
| window.Echo.private('channel-name').listen('MessageEvent', e => { | |
| console.log("Message", e.message); | |
| }); | |
| ``` | |
| The code aboce set user `codewithgun` with password `gunwithcode` for authentication purpose. Besides that, we also changed laravel echo to listen on private channel instead of the public channel by changing `window.Echo.channel` to `window.Echo.private`. | |
| > Remember to run `npm run dev` to rebundle the `bootstrap.js` | |
| Lastly, by default laravel application commented out application service provider for `BroadcastServiceProvider.php`, open `config/app.php` and uncomment the following code. | |
| ``` | |
| App\Providers\BroadcastServiceProvider::class, | |
| ``` | |
| Now everything is done, run `php artisan serve` and `php artisan websocket:serve` to test on it. | |
| > Please take note that there was no security consideration was taken, and most of the thing was hardcoded. However, it should be enough to serve as the basis for customization. |
This blog really helped me, thanks.
it's possible to turn it local ?
You did not specify PUSHER_APP_CLUSTER. I doubt the code runs without it. Especially if you would set encrypted: true
Great help
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This material was really helpful to me.
Please continue to write good articles in the future.