Sockudo
Getting Started

Authentication

Sign private, presence, encrypted, and user authentication requests with trusted server SDKs.

Sockudo never trusts client-provided access to protected channels. Clients request a subscription, your application server decides whether the user is allowed, then the server returns a signed response.

Channel authorization flow

  1. Client subscribes to private-orders or presence-room.
  2. The client SDK sends socket_id and channel_name to your auth endpoint.
  3. Your backend validates the current user and channel policy.
  4. A server SDK signs the response using the app secret.
  5. Sockudo validates the signature and completes the subscription.

Node.js auth endpoint

import express from "express";
import { Sockudo } from "sockudo";

const app = express();
app.use(express.urlencoded({ extended: false }));

const sockudo = new Sockudo({
  appId: process.env.SOCKUDO_APP_ID!,
  key: process.env.SOCKUDO_APP_KEY!,
  secret: process.env.SOCKUDO_APP_SECRET!,
  host: "127.0.0.1",
  port: 6001,
  useTLS: false,
});

app.post("/sockudo/auth", (req, res) => {
  const { socket_id, channel_name } = req.body;
  const user = requireUser(req);

  if (!canAccessChannel(user, channel_name)) {
    return res.status(403).json({ error: "Forbidden" });
  }

  if (channel_name.startsWith("presence-")) {
    return res.json(
      sockudo.authorizeChannel(socket_id, channel_name, {
        user_id: user.id,
        user_info: { name: user.name, role: user.role },
      }),
    );
  }

  return res.json(sockudo.authorizeChannel(socket_id, channel_name));
});

Client configuration

import Sockudo from "@sockudo/client";

const client = new Sockudo("app-key", {
  wsHost: "127.0.0.1",
  wsPort: 6001,
  forceTLS: false,
  channelAuthorization: {
    endpoint: "/sockudo/auth",
  },
});

client.subscribe("private-orders");

User authentication

User authentication signs a connection identity. It powers watchlists and user-targeted server events.

app.post("/sockudo/user-auth", (req, res) => {
  const user = requireUser(req);

  res.json(
    sockudo.authenticateUser(req.body.socket_id, {
      id: user.id,
      user_info: { name: user.name },
    }),
  );
});

Protocol V2 capability tokens

Protocol V2 clients may authenticate the WebSocket connection with a short-lived capability token:

const client = new Sockudo("app-key", {
  protocolVersion: 2,
  auth: {
    endpoint: "/sockudo/token",
  },
});

Your backend issues the JWT after authenticating the user. Never ship the app secret to browsers, mobile apps, or untrusted clients.

Node.js example:

import jwt from "jsonwebtoken";
import crypto from "node:crypto";

app.post("/sockudo/token", (req, res) => {
  const user = requireUser(req);
  const now = Math.floor(Date.now() / 1000);

  const capability = {
    "private-orders:*": ["subscribe", "history"],
    "presence-orders:*": ["subscribe", "presence"],
    "private-orders:input": ["publish"],
  };

  const token = jwt.sign(
    {
      "x-sockudo-capability": JSON.stringify(capability),
      "x-sockudo-client-id": user.id,
      iat: now,
      exp: now + 3600,
      jti: crypto.randomUUID(),
    },
    process.env.SOCKUDO_APP_SECRET!,
    {
      algorithm: "HS256",
      header: { kid: process.env.SOCKUDO_APP_KEY! },
    },
  );

  res.json({ token });
});

Rust example:

use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use serde::Serialize;
use std::collections::BTreeMap;

#[derive(Serialize)]
struct Claims {
    #[serde(rename = "x-sockudo-capability")]
    capability: String,
    #[serde(rename = "x-sockudo-client-id")]
    client_id: String,
    iat: i64,
    exp: i64,
    jti: String,
}

fn issue_token(app_key: &str, app_secret: &str, client_id: &str) -> anyhow::Result<String> {
    let now = chrono::Utc::now().timestamp();
    let mut capability = BTreeMap::new();
    capability.insert("private-orders:*", vec!["subscribe", "history"]);

    let mut header = Header::new(Algorithm::HS256);
    header.kid = Some(app_key.to_owned());

    Ok(encode(
        &header,
        &Claims {
            capability: serde_json::to_string(&capability)?,
            client_id: client_id.to_owned(),
            iat: now,
            exp: now + 3600,
            jti: uuid::Uuid::new_v4().to_string(),
        },
        &EncodingKey::from_secret(app_secret.as_bytes()),
    )?)
}

Token rules:

  • Header kid must equal the Sockudo app key and alg must be HS256.
  • x-sockudo-capability is a stringified JSON map from channel pattern to operations.
  • Operations are publish, subscribe, history, and presence.
  • Patterns are exact names, namespace prefixes such as orders:*, or *; matching is case-sensitive.
  • x-sockudo-client-id is the verified identity used for presence and user-limited channels.
  • iat, exp, and jti are required. Tokens may live at most 24 hours; 1 hour or less is recommended.
  • Clients refresh in place with sockudo:auth carrying { "token": "..." }. Expired tokens emit sockudo:token_expired with code 40142 and close after a 30 second grace window.

Encrypted channels

Encrypted channels start with private-encrypted-. The server signs the subscription and returns a per-channel shared secret derived from your encryption master key.

const sockudo = new Sockudo({
  appId: "app-id",
  key: "app-key",
  secret: "app-secret",
  host: "127.0.0.1",
  port: 6001,
  encryptionMasterKeyBase64: process.env.SOCKUDO_ENCRYPTION_MASTER_KEY!,
});

Keep the master key outside the browser. Client SDKs only receive the derived shared secret for authorized encrypted channels.

Push authorization boundary

Push registration and push publish helpers are also authentication boundaries. Mobile and browser clients should call your backend proxy, not Sockudo directly with app secrets.

app.post("/sockudo/push", async (req, res) => {
  const user = requireUser(req);
  await assertDeviceBelongsToUser(user, req.body.device_id);

  const response = await sockudo.publishPush({
    recipients: [{ type: "client", client_id: user.id }],
    payload: req.body.payload,
    sync: false,
  });

  res.status(202).json(response);
});

Security checklist

  • Read socket_id and channel_name from the request body; do not accept them from query parameters unless your framework requires it.
  • Authenticate the user before signing anything.
  • Enforce channel ownership and tenant boundaries before calling the SDK signing helper.
  • Return presence user data that is safe for all channel members to see.
  • Keep app secrets, webhook secrets, push provider credentials, and encryption master keys server-side only.
  • Issue capability tokens from a backend endpoint only; do not mint them in client code.
  • Use short request timeouts and structured audit logs for denied auth attempts.

On this page