Customized authentication will be used in this gist instead of default Auth facade provided by Laravel
composer create-project laravel-laravel your-project-name
cd your-project-name
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.
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.
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 thebootstrap.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.
it's possible to turn it local ?