CloudSignal Docs
Getting StartedSecurity

Server-side tokens

Mint short-lived MQTT credentials for browser clients. The secure way to connect web applications to CloudSignal.

Server-side tokens are the secure way to connect browser clients to CloudSignal. Instead of shipping long-lived credentials in JavaScript, your server mints short-lived tokens that browsers use to connect.

Why server-side tokens?

Browser code is inherently exposed:

RiskWhy it matters
View sourceAnyone can read your JavaScript
DevToolsNetwork requests reveal credentials
ExtensionsBrowser extensions can intercept data

Never do this:

// BAD: credentials visible in browser
const client = mqtt.connect('wss://connect.cloudsignal.app:18885/', {
  username: 'admin@org_k7xm4pqr2n5t',  // Exposed
  password: 'permanent-password',       // Exposed
});

With server-side tokens:

BenefitWhat it does
Secrets stay server-sidePermanent credentials never reach the browser
Short-livedTokens default to 60-minute TTL
Scoped permissionsTokens inherit a specific ACL rule set
Self-expiringCompromised tokens stop working quickly

How it works

Server-side token flow
Browser
Your server
Token API
MQTT broker
1. Request token
2. Mint token
3. Return token
4. Return credentials
5. Connect with token

Get your secret key

  1. Open your CloudSignal dashboard
  2. Go to Dashboard -> API Keys
  3. Create a new secret key (starts with sk_). Legacy cs_ keys are still accepted.
  4. Store it in an environment variable, never ship it to the browser.

Create the token endpoint

On your server, create an endpoint that mints tokens for the authenticated user:

// Next.js API route: app/api/mqtt-token/route.ts
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth'; // your auth library

export async function POST() {
  // 1. Verify the user is authenticated
  const session = await auth();
  if (!session?.user?.email) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // 2. Mint a token from CloudSignal
  const response = await fetch('https://api.cloudsignal.app/v2/tokens/create', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      organization_id: process.env.CLOUDSIGNAL_ORG_ID,
      secret_key: process.env.CLOUDSIGNAL_SECRET_KEY,
      user_email: session.user.email,
    }),
  });

  if (!response.ok) {
    console.error('Token generation failed:', await response.text());
    return NextResponse.json({ error: 'Token generation failed' }, { status: 502 });
  }

  const data = await response.json();
  return NextResponse.json({
    username: data.mqtt_credentials.username,
    password: data.mqtt_credentials.password,
    tokenId: data.token_id,
    expiresAt: data.expires_at,
    refreshAt: data.refresh_recommended_at,
  });
}

Use the token in the browser

// hooks/use-mqtt.ts
'use client';

import CloudSignal from '@cloudsignal/mqtt-client';
import { useEffect, useState } from 'react';

export function useMqtt(orgId: string, accessToken: string) {
  const [client, setClient] = useState<CloudSignal | null>(null);
  const [status, setStatus] = useState<'connecting' | 'connected' | 'error'>('connecting');

  useEffect(() => {
    let active = true;
    const c = new CloudSignal({ preset: 'desktop' });

    c.connectWithToken({ organizationId: orgId, externalToken: accessToken })
      .then(() => {
        if (!active) {
          c.destroy();
          return;
        }
        setClient(c);
        setStatus('connected');
      })
      .catch(() => setStatus('error'));

    return () => {
      active = false;
      c.destroy();
    };
  }, [orgId, accessToken]);

  return { client, status };
}

Token API reference

Create token

POST https://api.cloudsignal.app/v2/tokens/create
Content-Type: application/json

{
  "organization_id": "org_k7xm4pqr2n5t",
  "secret_key": "sk_...",
  "user_email": "user@example.com",
  "integration_id": "optional-source-tag",
  "replace_existing": true
}

Response

{
  "access_token": "tkn_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "mqtt_credentials": {
    "username": "abc123@org_k7xm4pqr2n5t",
    "password": "tkn_..."
  },
  "token_id": "uuid",
  "user_email": "user@example.com",
  "provider": "cloudsignal",
  "expires_at": "2026-03-27T14:00:00Z",
  "refresh_recommended_at": "2026-03-27T13:54:00Z"
}

Token permissions

Tokens inherit the organization's default ACL rule set. To scope them further, attach a rule set via the integration_id request field, the token service applies the ACL bound to that integration at mint time. Manage rule sets and integrations from Dashboard -> Access Control.

Token ACLs can only restrict permissions, never expand them beyond what's allowed for the organization.

Token refresh

The @cloudsignal/mqtt-client SDK refreshes tokens automatically. With autoRefresh: true (the default), the SDK renews credentials before refresh_recommended_at and reconnects without dropping subscriptions. You don't need to manage timers, refresh endpoints, or reconnects yourself.

import CloudSignal from '@cloudsignal/mqtt-client';

const client = new CloudSignal({
  preset: 'desktop',
  tokenServiceUrl: 'https://api.cloudsignal.app',
  // autoRefresh defaults to true, refreshBufferSeconds defaults to 60
});

await client.connectWithToken({
  organizationId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_ID,
  externalToken: session.access_token, // or secretKey on the server
});

// Optional: react to refresh lifecycle events
client.onAuthError = (err) => console.error('auth error', err);

To disable automatic refresh and handle it yourself, pass autoRefresh: false to the constructor and call your own refresh endpoint when needed. Most apps don't need to.

Security best practices

Authenticate users first

// Good: verify user before issuing a token
export async function POST(request: Request) {
  const session = await auth();
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  // Then mint the token
}

// Bad: issuing tokens for anonymous users
export async function POST(request: Request) {
  // No auth check, anyone can get tokens
  const token = await generateToken();
  return NextResponse.json(token);
}

Scope permissions tightly

// Good: user can only access their own topics
const token = await generateToken({
  user_id: userId,
  acl: {
    publish: [`users/${userId}/messages`],
    subscribe: [`users/${userId}/#`],
  },
});

// Bad: user can access everything
const token = await generateToken({
  user_id: userId,
});

Use short TTLs

// For highly sensitive applications
const token = await generateToken({
  user_id: userId,
  ttl: 900, // 15 minutes
});

// For typical web apps
const token = await generateToken({
  user_id: userId,
  ttl: 3600, // 1 hour
});

Log token generation

export async function POST(request: Request) {
  const session = await auth();

  const token = await generateToken({ /* ... */ });

  // Log for security auditing
  console.log({
    event: 'mqtt_token_generated',
    userId: session.user.id,
    ip: request.headers.get('x-forwarded-for'),
    userAgent: request.headers.get('user-agent'),
    tokenExpiry: token.expires_at,
  });

  return NextResponse.json(token);
}

On this page