Salem Nzeukwu Full-stack software engineer. Self-confessed positive vibes addict. Passionate about making stuff that looks and works great!

How to set up Laravel WebSockets on a subdomain

8 min read 2328

How To Set Up Laravel WebSockets On A Subdomain

WebSockets are an essential part of many modern web applications. With WebSockets, you can create applications that update in real-time without the need to reload the page or continually poll the server for changes.

Laravel supports two server-side broadcasting drivers out of the box: Pusher and Ably. While these two solutions may be easier to set up and can provide additional functionalities, they are both commercial solutions. This makes them unideal if you’re on a tight budget or if you’re simply looking for an open source solution.

Laravel WebSockets is a great open source alternative. It allows you to easily add WebSocket support to your Laravel >5.7 application. It comes with a debug dashboard and real-time statistics, among other features.

In this tutorial, we are going to discuss how to set up Laravel WebSockets on a subdomain. Specifically, we’ll go over the following topics:

The relationship between Laravel WebSockets and Pusher

Before we begin learning how to set up and use Laravel WebSockets on a subdomain, let’s go over some background information on the relationship between Laravel WebSockets and Pusher to prevent any confusion.

When using Laravel WebSockets, you are supposed to set Laravel’s BROADCAST_DRIVER configuration to pusher. You should also set other pusher configurations, like PUSHER_APP_ID, PUSHER_APP_KEY, and PUSHER_APP_SECRET, as well as install the pusher/pusher-php-server composer package.

This makes the package seem like a Pusher software development kit (SDK) that requires Pusher credentials to work. But this is not the case. You actually don’t need Pusher credentials to use Laravel WebSockets!

The main idea behind Laravel WebSockets is to replace the Pusher driver without letting the Laravel framework know about this replacement.

When you are using Laravel WebSockets, Laravel thinks you are using the Pusher driver and will enable its functionalities. Under the hood, however, these functionalities have been replaced by the Laravel WebSockets package.

Most importantly, the package replaces calls to the Pusher server with calls to our own server, where the package then handles the requests. It also implements the Pusher message protocol. As a result, all existing packages and applications that support Pusher work with the package as well.

Project and domain structure

If you have a standard Laravel setup where both the frontend and backend are by Laravel in one root folder, you may have less trouble setting up this package. Going through the package documentation should get you up and running in no time.

If your application’s frontend is completely separated from the backend, you’ll have to set things up a little differently. This kind of setup typically requires two separate repositories or at least two separate folders. Take awesomeapp-backend and awesomeapp-frontend for example.

You then need to set up two subdomains to point to each folder, like this:

  • awesomeapp.test or app.awesomeapp.test could point to your frontend folder called awesomeapp-frontend
  • api.awesome.test could point to your backend folder called awesomeapp-backend

When you have a setup like this, one thing you will most likely have to deal with is CORS.

Handling CORS

A webpage may freely load images, stylesheets, scripts, iframes, and videos from a different origin. However, certain cross-domain requests, such as AJAX requests, are forbidden by default by the same-origin security restriction implemented by all major web browsers.



Cross-Origin Resource Sharing (CORS) defines a way to specify exceptions to this restriction. It is a mechanism in which a browser and server can interact to determine whether it is safe to allow a cross-origin request.

How does CORS affect us?

So what does this mean for us? Well, our subdomains app.awesomeapp.test and api.awesome.test count as different origins, despite having the same domain host.

So, when we set up laravel-echo in our frontend subdomain, app.awesomeapp.test, the library will try to make an AJAX request to the backend subdomain, api.awesomeapp.test. Because they will be seen as separate origins by the browser, the request will fail…

…unless we enable CORS. Thankfully, enabling CORS in Laravel is pretty easy.

Enabling CORS in Laravel

To configure CORS for our Laravel WebSocket server, open the cors.php configuration file from the config folder of your Laravel installation. Add Laravel’s broadcasting/auth route to the paths array and set the supports_credentials option to true.

Under the hood, the supports_credentials option sets your application’s [Access-Control-Allow-Credentials] header to a value of true:

// config/cors.php
return [
    'paths' => ['api/*', 'sanctum/csrf-cookie', 'broadcasting/auth'],
    // ...
    'supports_credentials' => true,
];

Alternatively, you may place the Broadcast::routes method call within your routes/api.php file. This prefixes the broadcasting/auth route with api/, removing the need to add the route to the paths array since the api/* path (every route with api prefix) is in the array by default.

When calling the Broadcast::routes method, you should specify the middleware to be used when making requests to the channel routes. This could be auth:api or auth:sanctum if you are using Laravel Sanctum.

// routes/api.php
Broadcast::routes(['middleware' => ['auth:sanctum']]);
// config/cors.php
return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    // ...
    'supports_credentials' => true,
];

Also, make sure that HTTP/AJAX requests from your frontend have the XMLHttpRequest.withCredentials option set to true. If you are using axios, this can be done by setting the withCredentials option on your application’s global axios instance like in the code below:

axios.defaults.withCredentials = true;

Finally, you should ensure your application’s session cookie is accessible from any subdomain of your root domain. This can be done by prefixing the domain with a leading . within your application’s config/session.php configuration file.

'domain' => '.example.com',

To make the configuration environmental agonistic, you should instead set this option using the SESSION_DOMAIN environmental variable within your .env file:

// config/session.php
'domain' => env('SESSION_DOMAIN', null),
# .env
# ...
SESSION_DOMAIN=.example.com

Now, let’s go ahead and set up Laravel WebSockets!

Installing laravel-websockets

Laravel WebSockets can be installed using composer.


More great articles from LogRocket:


composer require beyondcode/laravel-websockets

The package comes with a migration to store statistical information about your WebSocket server. You can skip this step if you do not plan on using this feature.

Run the following command to publish the migration file:

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

Then, run the migrations:

php artisan migrate

Next, publish the Laravel WebSocket configuration file by running the following command:

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

As we discussed, the Laravel WebSockets package works in combination with Pusher, so we also need to install the official Pusher PHP SDK:

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

Make sure to use Pusher as your broadcasting driver. This can be done by setting the BROADCAST_DRIVER environment variable within your .env file:

BROADCAST_DRIVER=pusher

Lastly, make sure that the APP_NAME, PUSHER_APP_ID, PUSHER_APP_KEY, and PUSHER_APP_SECRET environmental variables are set in your .env file.

N.B., It does not matter what you set as your PUSHER_ variables, just make sure they are unique for each project.

PUSHER_APP_ID=pusherid
PUSHER_APP_KEY=pusherkey
PUSHER_APP_SECRET=pushersecret
PUSHER_APP_CLUSTER=pushercluster

Laravel WebSockets configurations

As mentioned earlier, when broadcasting events from your Laravel application, the default behavior of the Pusher driver is to send the event information to the official Pusher server. But since the Laravel WebSockets package comes with its own Pusher API implementation, we need to tell Laravel to send the event information to our own server.

We do this by adding the host and port configuration key to the pusher section of our config/broadcasting.php file. The default port of the Laravel WebSocket server is 6001.

'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'
    ],
],

Configuring WebSocket apps

Laravel WebSockets uses the concept of apps to support multitenancy out of the box. This allows you to host the package separately from your current Laravel application and serve multiple WebSocket applications with one server.

The default app uses your existing Pusher configuration. This should do for most use cases.

'apps' => [
    [
        'id' => env('PUSHER_APP_ID'),
        'name' => env('APP_NAME'),
        'key' => env('PUSHER_APP_KEY'),
        'secret' => env('PUSHER_APP_SECRET'),
        'enable_client_messages' => false,
        'enable_statistics' => true,
    ],
],

Be sure to use the same id, key, and secret from your broadcasting configuration section, otherwise broadcasting events from Laravel will not work.

Client messages

Usually, all WebSocket messages go through your Laravel application before being broadcast to other users. But sometimes you may want to send an event directly from one client to another without it going to the server.

For example, take a typing event in a chat application. You can configure this option for each app in your config/websockets.php configuration file using the enable_client_messages key.

You should use this feature with care since these event messages originate from other users and could be subject to tampering.

Statistics

As mentioned earlier, the Laravel WebSockets package comes with a dashboard to monitor statistical information about your WebSocket server. To enable or disable this feature for any of your apps, you can modify the enable_statistics option.

Enabling event broadcasting in Laravel

To enable event broadcasting in Laravel, you need to register the App\Providers\BroadcastServiceProvider. You can do this by adding App\Providers\BroadcastServiceProvider::class to the providers array of your config/app.php file:

'providers' => [
   // ...
    App\Providers\BroadcastServiceProvider::class,
    // ...
],

In new Laravel applications, this provider is already in the array, you just need to uncomment it.
The BroadcastServiceProvider contains the code necessary to register the broadcast authorization routes and callbacks.

If you added the Broadcast::routes method within the routes/api.php file earlier, you can safely comment or remove this line in the boot method of the BroadcastServiceProvider.

public function boot()
{
    // Broadcast::routes();
    require base_path('routes/channels.php');
}

That’s all for the backend, now let’s set up the frontend app!

Setting up Laravel Echo

Laravel Echo is a JavaScript library that makes it very easy to subscribe to channels and listen for events broadcasted by your Laravel server-side broadcasting driver. In this case, it’s Laravel Websockets’ Pusher API implementation.

Echo can easily be installed via the npm package manager. We will also install pusher-js since the Laravel Websockets package uses the Pusher Channels broadcaster:

npm install --save-dev laravel-echo pusher-js

To make Laravel Echo work with Laravel WebSockets, we need to pay attention to some configuration options when initializing Laravel Echo. Specifically, we need to add the wsHost and wsPort parameters and point them to our Laravel WebSocket server host and port.

In order for Laravel WebSocket authorization requests to succeed, we also need to provide a custom authentication endpoint using authEndpoint and an authorization header using auth.headers.Authorization.

When the authEndpoint option is omitted, Laravel Echo will try to authenticate using the same host that it runs on. But in our case, since the WebSocket server is on a different subdomain, the request will fail.

N.B., If your Broadcast::routes method call is in the App\Providers\BroadcastServiceProvider file, which is the default, the value of your authEndpoint should look something like: http://api.awesomeapp.test/broadcasting/auth.

If you placed the method call within the routes/api.php file, then it should be something like: http://api.awesomeapp.test/api/broadcasting/auth.

import Echo from "laravel-echo"
window.Pusher = require('pusher-js');

window.Echo = new Echo({
  broadcaster: 'pusher',
  key: 'pusherkey',
  wsHost: 'api.websockets.test',
  wsPort: 6001,
  // wssPort: 6001,
  forceTLS: false,
  // encrypted: true,
  disableStats: true,
  auth: { headers: { Authorization: 'Bearer sometoken' } },
  authEndpoint: 'http://api.awesomeapp.test/api/broadcasting/auth', // OR
  // authEndpoint: 'http://api.awesomeapp.test/broadcasting/auth',
})

Alternatively, you can configure a custom authorizer when initializing Laravel Echo. This allows you to configure Laravel Echo to use your global axios instance, which should be properly configured for cross-domain requests and with the right authorization headers.

import Echo from "laravel-echo"
window.Pusher = require('pusher-js');

window.Echo = new Echo({
  broadcaster: 'pusher',
  key: 'pusherkey',
  wsHost: 'api.awesomeapp.test',
  wsPort: 6001,
  // wssPort: 6001, // For SSL
  forceTLS: false,
  // encrypted: true, // For SSL
  disableStats: true,
  authorizer: (channel, options) => {
    return {
      authorize: (socketId, callback) => {
        axios
          .post('/api/broadcasting/auth', {
            socket_id: socketId,
            channel_name: channel.name,
          })
          .then((response) => {
            callback(false, response.data)
          })
          .catch((error) => {
            callback(true, error)
          })
      },
    }
  },
})

Make sure that the value of key is the same as that of the PUSHER_APP_KEY in your backend configuration.

By default, the Pusher JavaScript client tries to send statistic information, but we can disable this using the disableStats option.

When using Laravel WebSockets in combination with a custom SSL certificate, be sure to use the wssPort option instead of wsPort. Also, use the encrypted option and set it to true.

And that’s it! Now you can use Laravel Echo features in combination with Laravel WebSockets.

Conclusion

In this article, we learned all about Laravel WebSockets. We discussed the relationship between the package and Pusher, how to handle CORS when working with multiple subdomains, and how to set up Laravel WebSockets in our application. We also learned how to set up Laravel Echo to work with Laravel WebSockets, especially when working with multiple subdomains.

While the main purpose of the laravel-websockets package is to make the Pusher JavaScript client or Laravel Echo as easy to use as possible, you are not limited to this use case or the Pusher protocol.

To learn more about how you might use the package in other use cases, check out the Laravel WebSockets documentation.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Salem Nzeukwu Full-stack software engineer. Self-confessed positive vibes addict. Passionate about making stuff that looks and works great!

Leave a Reply