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:
- The message comes from someone who knows the secret
- 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
- Client attempts to subscribe to a private channel
- Client library sends auth request to your server
- Your server generates auth signature
- Client sends signature to Sockudo
- 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
- Build query string with auth parameters (sorted alphabetically)
- Create string to sign:
METHOD\nPATH\nQUERY_STRING
- Generate HMAC-SHA256 signature
- 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)
Implement proper authorization
- Verify user permissions before signing auth tokens
- Use session authentication on auth endpoints
- Log authentication failures
- Implement rate limiting on auth endpoints
Use HTTPS/WSS in production
- 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