Skip to main content

Overview

Sockudo uses HMAC-SHA256 signatures to authenticate private channels and API requests. This ensures that:
  • Only authorized clients can subscribe to private/presence channels
  • API requests come from authenticated sources
  • Messages cannot be forged or tampered with
Authentication is required for:
  • Private channels (private-*)
  • Presence channels (presence-*)
  • Encrypted channels (private-encrypted-*)
  • HTTP API requests

How HMAC Authentication Works

HMAC (Hash-based Message Authentication Code) uses a shared secret to create a signature that proves:
  1. The message comes from someone who knows the secret
  2. The message hasn’t been modified in transit
Key components:
  • App Key - Public identifier (included in signatures)
  • App Secret - Private key (never transmitted, only used for signing)
  • Signature - HMAC-SHA256 hash of message data
Never expose your app secret in client-side code. Always generate signatures on your backend server.

Application Credentials

Each application in Sockudo has three credentials:
{
  "id": "my-app",
  "key": "app-key-123",
  "secret": "app-secret-xyz",
  "enabled": true
}
  • id: Application identifier
  • key: Public key (safe to expose in client code)
  • secret: Private key (keep secure on your backend)

Private Channel Authentication

Client Flow

  1. Client attempts to subscribe to a private channel
  2. Client library sends auth request to your server
  3. Your server generates auth signature
  4. Client sends signature to Sockudo
  5. Sockudo validates signature and allows subscription

Client-Side Setup

const pusher = new Pusher('app-key-123', {
  cluster: 'mt1',
  wsHost: 'your-sockudo-server.com',
  authEndpoint: '/pusher/auth',  // Your server endpoint
  auth: {
    headers: {
      'X-CSRF-Token': csrfToken  // Optional: your auth headers
    }
  }
});

// Subscribe to private channel
const channel = pusher.subscribe('private-chat-room');
When subscribing, the library will POST to /pusher/auth with:
{
  "socket_id": "1234.5678",
  "channel_name": "private-chat-room"
}

Server-Side Auth Endpoint

Your backend must generate and return the auth signature:
const express = require('express');
const crypto = require('crypto');

const app = express();
const APP_KEY = 'app-key-123';
const APP_SECRET = 'app-secret-xyz';

app.post('/pusher/auth', (req, res) => {
  const socketId = req.body.socket_id;
  const channel = req.body.channel_name;
  
  // Verify user is authorized for this channel
  if (!isUserAuthorized(req.user, channel)) {
    return res.status(403).send('Forbidden');
  }
  
  // Generate signature
  const stringToSign = `${socketId}:${channel}`;
  const hmac = crypto.createHmac('sha256', APP_SECRET);
  hmac.update(stringToSign);
  const signature = hmac.digest('hex');
  
  res.send({
    auth: `${APP_KEY}:${signature}`
  });
});

Presence Channel Authentication

Presence channels require additional channel_data with user information:
app.post('/pusher/auth', (req, res) => {
  const socketId = req.body.socket_id;
  const channel = req.body.channel_name;
  
  // User data to associate with this member
  const channelData = {
    user_id: req.user.id,
    user_info: {
      name: req.user.name,
      avatar: req.user.avatar
    }
  };
  
  // Sign with channel_data included
  const channelDataStr = JSON.stringify(channelData);
  const stringToSign = `${socketId}:${channel}:${channelDataStr}`;
  const hmac = crypto.createHmac('sha256', APP_SECRET);
  hmac.update(stringToSign);
  const signature = hmac.digest('hex');
  
  res.send({
    auth: `${APP_KEY}:${signature}`,
    channel_data: channelDataStr
  });
});
Important: The user_id must be unique per user. Multiple connections from the same user_id are treated as one presence member.

HTTP API Authentication

API requests must be signed using Pusher’s authentication scheme:

Request Signing Process

  1. Build query string with auth parameters (sorted alphabetically)
  2. Create string to sign: METHOD\nPATH\nQUERY_STRING
  3. Generate HMAC-SHA256 signature
  4. Add signature to query parameters

Example API Request

const crypto = require('crypto');
const querystring = require('querystring');

function signRequest(method, path, body) {
  const timestamp = Math.floor(Date.now() / 1000);
  const bodyMd5 = crypto.createHash('md5').update(body).digest('hex');
  
  // Query parameters (sorted alphabetically)
  const params = {
    auth_key: APP_KEY,
    auth_timestamp: timestamp,
    auth_version: '1.0',
    body_md5: bodyMd5
  };
  
  // Create string to sign
  const queryString = querystring.stringify(params);
  const stringToSign = `${method}\n${path}\n${queryString}`;
  
  // Generate signature
  const signature = crypto
    .createHmac('sha256', APP_SECRET)
    .update(stringToSign)
    .digest('hex');
  
  params.auth_signature = signature;
  
  return querystring.stringify(params);
}

// Trigger event
const path = '/apps/my-app/events';
const body = JSON.stringify({
  name: 'my-event',
  channel: 'my-channel',
  data: '{"message": "hello"}'
});

const query = signRequest('POST', path, body);
const url = `http://localhost:6001${path}?${query}`;

fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: body
});

Timestamp Validation

Sockudo validates that request timestamps are within 10 minutes of server time:
|server_time - auth_timestamp| < 600 seconds
This prevents replay attacks. Ensure your servers have synchronized clocks (use NTP).

User Authentication (Sign In)

Sockudo supports user authentication for features like watchlists:
// Client requests sign-in token from your server
fetch('/pusher/user-auth', {
  method: 'POST',
  body: JSON.stringify({ socket_id: pusher.connection.socket_id })
})
.then(res => res.json())
.then(data => {
  pusher.signin();
});
Server generates user auth token:
app.post('/pusher/user-auth', (req, res) => {
  const socketId = req.body.socket_id;
  const userData = JSON.stringify({
    id: req.user.id,
    user_info: { name: req.user.name }
  });
  
  // Sign: socket_id::user::user_data
  const stringToSign = `${socketId}::user::${userData}`;
  const signature = crypto
    .createHmac('sha256', APP_SECRET)
    .update(stringToSign)
    .digest('hex');
  
  res.json({
    auth: signature,
    user_data: userData
  });
});

Security Best Practices

  • Never commit secrets to version control
  • Use environment variables for secrets
  • Rotate secrets periodically
  • Use different secrets per environment (dev/staging/prod)
  • Verify user permissions before signing auth tokens
  • Use session authentication on auth endpoints
  • Log authentication failures
  • Implement rate limiting on auth endpoints
  • Sanitize channel names and user data
  • Limit user_info size in presence channels
  • Validate socket_id format
  • Reject malformed requests early
  • Enable TLS for all connections
  • Use valid SSL certificates
  • Redirect HTTP to HTTPS
  • Configure HSTS headers
  • Track failed authentication attempts
  • Alert on unusual patterns
  • Log signature validation failures
  • Monitor for replay attacks

Testing Authentication

Test signature generation manually:
# Generate signature for testing
echo -n "1234.5678:private-test" | \
  openssl dgst -sha256 -hmac "your-secret" -hex
Validate with Sockudo:
// Test subscription with manual auth
const socket = new WebSocket('ws://localhost:6001/app/your-key');

socket.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  
  if (msg.event === 'pusher:connection_established') {
    const data = JSON.parse(msg.data);
    const socketId = data.socket_id;
    
    // Subscribe with manual signature
    socket.send(JSON.stringify({
      event: 'pusher:subscribe',
      data: {
        channel: 'private-test',
        auth: 'your-key:generated-signature'
      }
    }));
  }
};

Troubleshooting

“Invalid signature” errors:
  • Verify app key and secret are correct
  • Check string to sign is formatted correctly
  • Ensure no extra whitespace in signature
  • Verify HMAC is using SHA256
“Timestamp expired” errors:
  • Check server clocks are synchronized (use NTP)
  • Verify timestamp is in Unix seconds (not milliseconds)
  • Ensure timezone handling is correct
“Forbidden” from auth endpoint:
  • Check user session is valid
  • Verify CSRF token if required
  • Review authorization logic
  • Check request headers are correct

Next Steps

Channels

Learn about channel types and features

HTTP API

Trigger events via authenticated REST API

Security

Production security configuration

Rate Limiting

Protect your server from abuse

Build docs developers (and LLMs) love