code-runner
Guides

Channel Auth

Authorizing a browser's private soketi channel — your backend's job.

To receive output, a browser subscribes to the private soketi channel private-run-<jobId>. Pusher's protocol requires private channels to be authorized: the client asks a server to sign its subscription. In code-runner, that signing is your backend's responsibility — it's the side that holds the SOKETI_APP_SECRET.

How private-channel auth works

When pusher-js subscribes to a private channel, it POSTs { socket_id, channel_name } to your configured authEndpoint. Your server must respond with an auth token:

auth = "<SOKETI_APP_KEY>:" + HMAC_SHA256("<socket_id>:<channel_name>", SOKETI_APP_SECRET)

The browser sends that token back to soketi, which verifies the HMAC with its copy of the secret and admits the subscription. This is the standard Pusher private-channel flow — code-runner just reuses it.

Don't hand-roll the HMAC. Use createChannelAuthorizer, which signs and also rejects any channel that isn't private-run-* so your endpoint can't be abused to authorize arbitrary channels:

import { createChannelAuthorizer } from '@teovilla/code-runner-sdk-node';

const authorize = createChannelAuthorizer({
  appKey: process.env.SOKETI_APP_KEY!,
  appSecret: process.env.SOKETI_APP_SECRET!,
});

app.post('/code-runner/auth', (req, res) => {
  try {
    const { socket_id, channel_name } = req.body;
    res.json(authorize(socket_id, channel_name)); // { auth: "<key>:<hmac>" }
  } catch {
    res.status(403).json({ error: 'forbidden channel' });
  }
});

Point your React provider's authEndpoint at this route. In a real app you'd also gate this route by your own session — only sign a channel for a user who is actually allowed to watch that job.

Doing it without the SDK

The signing is ~5 lines in any language. Node, for reference:

import { createHmac } from 'node:crypto';

function signChannel(socketId: string, channelName: string): string {
  const stringToSign = `${socketId}:${channelName}`;
  const hmac = createHmac('sha256', process.env.SOKETI_APP_SECRET!)
    .update(stringToSign)
    .digest('hex');
  return `${process.env.SOKETI_APP_KEY}:${hmac}`;
}
// respond with: { auth: signChannel(socket_id, channel_name) }

The trust boundary

The SOKETI_APP_SECRET is read from the environment only. It is never written to Redis, never returned by any code-runner endpoint, and never sent to the browser. It must stay inside your backend. The SOKETI_APP_KEY is public and safe in the browser; the secret is not.

The built-in helper (local demos only)

For convenience, the API can expose its own channel-auth endpoint when ENABLE_CHANNEL_AUTH=true:

POST /v1/channel-auth
  { "socket_id": "123.456", "channel_name": "private-run-<jobId>" }
  → 200 { "auth": "<app_key>:<hmac>" }

It performs the exact same HMAC. It exists so a single service (like the bundled stub) can handle both execution and channel auth during local development. In production, prefer signing in your own backend so the secret lives with your session logic and the auth decision can respect your authorization rules. It rejects any non private-run- channel with 403.

On this page