Skip to content
Docs/Auth & Signatures
Docs/Authentication

Auth & Signatures

Complete HMAC-SHA256 signature algorithms for channels, user signin, and REST API.

Overview

Socketo utilizes standard HMAC-SHA256 signatures to authenticate channel subscriptions, client user sessions, and backend REST API requests.

1. Private Channel Authorization

Clients subscribing to private-* channels must submit an authorization token signed by the application backend.

plaintext
String to sign: ${socket_id}:${channel_name}
Algorithm:      HMAC-SHA256(secret, string_to_sign)
Response:       { "auth": "${app_key}:${signature}" }

2. Presence Channel Authorization

Subscriptions to presence-* channels include serialized user identity metadata in the signature calculation:

plaintext
String to sign: ${socket_id}:${channel_name}:${channel_data}
Algorithm:      HMAC-SHA256(secret, string_to_sign)
Response:       { "auth": "${app_key}:${signature}", "channel_data": "${channel_data}" }

channel_data must be a valid JSON string (e.g. JSON.stringify({ user_id: "...", user_info: { ... } })).

3. User Authentication (pusher:signin)

Connection-level user sign-in links an authenticated user identity to an active socket connection:

plaintext
String to sign: ${socket_id}::user::${user_data}
Algorithm:      HMAC-SHA256(secret, string_to_sign)
Response:       { "auth": "${app_key}:${signature}", "user_data": "${user_data}" }

user_data must be a valid JSON string (e.g. JSON.stringify({ id: "...", user_info: { ... } })).

4. REST API Request Signing

All HTTP requests to /apps/:id/* require query-string HMAC authentication.

Authentication Query Parameters

ParameterTypeDescription
auth_keystringPublic application key (app.id).
auth_timestampnumberUnix timestamp in seconds (valid within ±600 seconds of server time).
auth_versionstringMust be exactly "1.0".
body_md5stringHex MD5 digest of the request body (required for POST, PUT, PATCH).
auth_signaturestringCalculated HMAC-SHA256 signature.

Canonical Signing Format

plaintext
METHOD
PATH
QUERY_STRING

Rules for QUERY_STRING:

  1. Exclude auth_signature.
  2. Convert all parameter keys to lowercase.
  3. Sort parameters lexicographically by key.
  4. Format as unescaped key=value pairs delimited by &.

TypeScript Reference Implementation

ts
import crypto from 'node:crypto'

// 1. Private Channel Auth
export function signPrivateChannel(socketId: string, channel: string, key: string, secret: string) {
  const toSign = `${socketId}:${channel}`
  const signature = crypto.createHmac('sha256', secret).update(toSign).digest('hex')
  return { auth: `${key}:${signature}` }
}

// 2. Presence Channel Auth
export function signPresenceChannel(socketId: string, channel: string, userData: object, key: string, secret: string) {
  const channelData = JSON.stringify(userData)
  const toSign = `${socketId}:${channel}:${channelData}`
  const signature = crypto.createHmac('sha256', secret).update(toSign).digest('hex')
  return { auth: `${key}:${signature}`, channel_data: channelData }
}

// 3. User Signin Auth
export function signUserAuth(socketId: string, userData: object, key: string, secret: string) {
  const dataString = JSON.stringify(userData)
  const toSign = `${socketId}::user::${dataString}`
  const signature = crypto.createHmac('sha256', secret).update(toSign).digest('hex')
  return { auth: `${key}:${signature}`, user_data: dataString }
}

// 4. REST API Signing
export function signRestRequest(
  method: string,
  path: string,
  key: string,
  secret: string,
  body?: string,
  extraParams: Record<string, string> = {},
) {
  const params: Record<string, string> = {
    auth_key: key,
    auth_timestamp: String(Math.floor(Date.now() / 1000)),
    auth_version: '1.0',
    ...extraParams,
  }

  if (body && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
    params.body_md5 = crypto.createHash('md5').update(body).digest('hex')
  }

  const sortedQuery = Object.keys(params)
    .map((k) => [k.toLowerCase(), params[k]])
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([k, v]) => `${k}=${v}`)
    .join('&')

  const toSign = `${method.toUpperCase()}\n${path}\n${sortedQuery}`
  const signature = crypto.createHmac('sha256', secret).update(toSign).digest('hex')

  return `${sortedQuery}&auth_signature=${signature}`
}