Blog

SocketBox: Security, Authentication, and Authorization

Jacob Beers February 27, 2026

Spread the word

Jacob Beers

February 27, 2026

Spread the word


Share your thoughts

SocketBox: Securing Your WebSockets with STOMP

Welcome back to our series on SocketBox the premier WebSocket module for CommandBox and the BoxLang runtime!

If you've been following along, we’ve already covered a lot of ground. In Part 1, we built our first WebSocket connection and saw how easy it is to achieve real-time, bi-directional communication. In Part 2, we tackled the real world by clustering our servers and deploying behind a load balancer, proving that solving WebSocket problems with more WebSockets is a perfectly valid (and powerful) strategy.

But now, we need to talk about the elephant in the room: Security.

By default, raw WebSockets are essentially open doors. Anyone who knows the URL can connect, send messages, and listen to the stream. In a production environment, you wouldn't let just anyone wander into your database, so why would you let them wander into your WebSocket channels? We need a way to verify who is connecting (Authentication) and control what they are allowed to see and do (Authorization).

Enter STOMP.

What is STOMP?

STOMP stands for Simple Text Oriented Messaging Protocol. If raw WebSockets are a blank sheet of paper, STOMP is a standardized envelope. It provides a formal structure for our WebSocket messages, including a "command" (like CONNECT, SEND, or SUBSCRIBE), a set of metadata "headers", and a message "body."

More importantly for us today, STOMP introduces built-in semantics for authentication, message subscriptions, and routing. SocketBox has a fully-featured STOMP broker built right in. To unlock it, we simply change our listener component to extend modules.socketbox.models.WebSocketSTOMP instead of WebSocketCore.

Exchanges and Topics

Before we lock the doors, we need to understand how messages travel within STOMP. When a publisher sends a message, they don't send it directly to a subscriber. Instead, they send it to an Exchange.

Think of an Exchange as a mail sorting room. It receives an incoming message and uses a set of binding rules to decide which "Destinations" (like topics or queues) should receive a copy. An exchange might route a message to zero destinations, one destination, or a thousand. Subscribers simply listen to their chosen destinations without ever needing to know who published the message.

You define these routing rules in the configure() method of your WebSocket.bx listener:

class extends="modules.socketbox.models.WebSocketSTOMP" {

    function configure() {
        return {
            // How often to send a heartbeat to keep connections alive (ms)
            "heartBeatMS" : 10000,

            // Configure our exchanges and bindings
            "exchanges" : {
                // The 'topic' exchange allows wildcard matching
                "topic" : {
                    "bindings" : {
                        // The * wildcard matches a single word segment
                        "alerts.*" : "destination1",
                        // The # wildcard matches zero or more word segments
                        "notifications.##" : "destination2"
                    }
                },
                // The 'fanout' exchange sends messages to ALL bindings
                "fanout" : {
                    "bindings" : {
                        "broadcasts" : [ "destination1", "destination2" ]
                    }
                }
            }
        };
    }
}

By setting up exchanges, you decouple your publishers from your subscribers, creating a highly scalable and flexible architecture.

Authentication: Who Are You?

Authentication is the process of verifying a user's identity. When a client attempts to establish a STOMP connection over their WebSocket, SocketBox intercepts the request and passes it to an authenticate() method inside your WebSocket.bx listener.

Here is the beauty of SocketBox: it doesn't force you into a specific security framework. You have complete freedom to implement whatever authentication strategy fits your application.

    /**
     * Authenticate incoming STOMP connections
     */
    public boolean function authenticate(
        required string login,
        required string passcode,
        string host,
        required any channel
    ) {
        // Example 1: Validating a JWT passed as the passcode
        if ( len( passcode ) ) {
            return validateJWT( passcode );
        }

        // Example 2: Traditional username and password check
        if ( len( login ) && len( passcode ) ) {
            return authenticateUser( login, passcode );
        }

        // If credentials don't match or are missing, deny the connection
        return false;
    }

In this example, validateJWT() and authenticateUser() are placeholder functions representing your app's internal logic. You might query a database or check a CacheBox instance. As long as this method returns true, the STOMP connection is established. If it returns false, SocketBox rejects the connection and keeps the gate closed.

Pro-Tip: You Can Use Cookies! Did you know that a WebSocket connection starts its life as a standard HTTP Upgrade request? That means any cookies present in the user's browser are sent along for the ride. Because SocketBox passes incoming messages to your app via an internal request, normal CGI variables, cookies, and session scopes work perfectly! If your user is already logged into your BoxLang web app via a traditional session cookie, you can simply check session.isLoggedIn or validate cookie.myAuthToken right inside your authenticate() and authorize() methods.

Authorization: What Can You Do?

Just because we know who a user is doesn't mean they should have access to everything. This is where Authorization comes in.

Every time a user tries to subscribe to a destination or publish a message to one, SocketBox fires the authorize() method. Let's look at two incredibly common, real-world authorization scenarios.

Scenario 1: Role-Based Authorization

Imagine you have a destination called /topic/admin-alerts. You want all users to be able to connect to the WebSocket server, but only administrators should be allowed to subscribe to or publish to this specific destination.

    public boolean function authorize(
        required string login,
        required string exchange,
        required string destination,
        required string access,
        required any channel
    ) {
        // The 'access' argument will be either "subscribe" or "publish"

        // Protect the admin topics
        if ( destination.startsWith( "/topic/admin" ) ) {
            // Check your internal system to see if this user has the admin role
            return userHasRole( login, "admin" );
        }

        // Allow access to other public destinations
        return true;
    }

With just a few lines of code, you've completely walled off a section of your real-time infrastructure. If a standard user attempts to subscribe to /topic/admin-alerts, the broker simply ignores the request.

Scenario 2: User-Specific Topics

Now consider a scenario where you want to send real-time notifications (like a "Your report has finished generating" alert) to a specific user. You don't want User A to see User B's notifications.

We can create a convention where users subscribe to a topic containing their own username, like /user/notifications/bob. We can enforce this convention in our authorize() method:

    public boolean function authorize(
        required string login,
        required string exchange,
        required string destination,
        required string access,
        required any channel
    ) {
        // Restrict access to user-specific destinations
        if ( destination.startsWith( "/user/notifications/" ) ) {

            // Extract the requested username from the end of the destination string
            var requestedUser = destination.listLast( "/" );

            // Only allow the user to subscribe if it's their own topic!
            return login == requestedUser;
        }

        // Default allow for un-protected routes
        return true;
    }

Now, when our server-side code finishes a task for Bob, it can safely publish a message directly to /user/notifications/bob, knowing with absolute certainty that only Bob can receive it.

Connecting from the Client

To test our secure setup, we need a client that speaks STOMP. While there are many libraries out there, we highly recommend @stomp/stompjs. It's robust, well-maintained, and handles the nuances of the protocol perfectly.

Here is how you initialize the connection from your browser, passing along the credentials required by our authenticate() method:

<script type="importmap">
  {
    "imports": {
      "@stomp/stompjs": "<https://ga.jspm.io/npm:@stomp/stompjs@7.0.0/esm6/index.js>"
    }
  }
</script>

<script type="module">
  import { Client } from '@stomp/stompjs';

  const client = new Client({
    brokerURL: 'ws://localhost:8080/ws',

    // Pass our credentials to the SocketBox authenticate() method
    connectHeaders: {
      login: 'bob',
      passcode: 'super-secret-jwt-or-password'
    },

    onConnect: (frame) => {
      console.log('Securely connected to STOMP broker!');

      // Bob successfully subscribes to his own secure queue
      client.subscribe('/user/notifications/bob', function( message ) {
        console.log('New notification: ', message.body);
      });

      // If Bob is NOT an admin, this subscription will simply be denied!
      client.subscribe('/topic/admin-alerts', function( message ) {
        console.log('Admin alert: ', message.body);
      });
    },

    onStompError: (error) => {
      console.error('STOMP error: ' + error.headers.message);
    }
  });

  client.activate();
</script>

By providing the login and passcode inside connectHeaders, the StompJS client automatically packages them into the initial CONNECT frame sent to the server. SocketBox unpackages them, hands them to your authentication logic, and immediately establishes a secure, real-time pipeline.

Try It Out!

As always, reading about code is good, but running it is better. I have updated our companion tutorial repository with a fully working example of everything we discussed today.

Clone the repository and check out the security-blog-3 tag to see the STOMP security in action:

git clone <https://github.com/jbeers/socketbox-intro.git>
cd socketbox-intro
git checkout security-blog-3

# Install dependencies and start the server
box install
box server start

Next Up

By embracing the STOMP protocol, SocketBox takes the headache out of WebSocket security. You get standard conventions for message formats while retaining total control over the business logic of your authentication and authorization rules.

We've covered the basics, we've clustered our servers, and now we've locked the doors. So, what's next?

In our fourth and final blog post of this series, titled "SocketBox: WebSocket Ideas and Examples," we are going to let our imaginations run wild. We will step away from the configuration files and focus entirely on recipes, use cases, and creative ways to integrate real-time features into your BoxLang and CFML applications.

Stay tuned, and happy coding!

Learn more at our Free Webinar

In this webinar, we will take a look at the SocketBox library and how it integrates with CommandBox and BoxLang. This is the culmination of the blog series covering different aspects of using websockets, like configuration, load-balancing, security, and implementation. The webinar will include an explanation of the library, how to set it up, as well as a live demonstration.

  • Online event
  • Friday, Mar 20 from 10 am to 11 am CST

Register for Free

Join the Ortus Community

Be part of the movement shaping the future of web development. Stay connected and receive the latest updates on, product launches, tool updates, promo services and much more.

Subscribe to our newsletter for exclusive content.

Follow Us on Social media and don’t miss any news and updates:

Add Your Comment

Recent Entries

Why Legacy CFML Applications Block Innovation

Why Legacy CFML Applications Block Innovation

APIs, OAuth, SSO and Cloud Services in a Modern Architecture

For many organizations, legacy CFML applications still run core business processes reliably. They generate revenue, process transactions and support customers every day.

The problem is not always stability.

The problem is velocity.

Over time, older ColdFusion or Lucee environments begin to limit what the organization can build next. Not because the business lacks vision, but because the underlying platf...

Cristobal Escobar
Cristobal Escobar
February 27, 2026
ColdFusion Security Isn’t Optional: 7 Hidden Risks Lurking in Mature CFML Environments

ColdFusion Security Isn’t Optional: 7 Hidden Risks Lurking in Mature CFML Environments

ColdFusion applications are often stable for years.

They keep running.

They serve users.

They “just work.”

And that stability creates a dangerous illusion:

“If nothing’s broken, we must be secure.”

In mature CFML environments — especially those running Adobe ColdFusion 2021, Adobe ColdFusion 2018, Adobe ColdFusion 2016, Adobe ColdFusion 11, Lucee 5.4, or Lucee 5.3 or older — risk rarely appears as a dramatic failure.

It accumulates quietly.

...

Cristobal Escobar
Cristobal Escobar
February 26, 2026
BoxLang Homebrew Installer Released

BoxLang Homebrew Installer Released

We're excited to announce the official BoxLang Homebrew tap — the easiest way to get BoxLang up and running on macOS (and Linux with Homebrew). One command, and you're in business.

Luis Majano
Luis Majano
February 26, 2026