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:
| Risk | Why it matters |
|---|---|
| View source | Anyone can read your JavaScript |
| DevTools | Network requests reveal credentials |
| Extensions | Browser 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:
| Benefit | What it does |
|---|---|
| Secrets stay server-side | Permanent credentials never reach the browser |
| Short-lived | Tokens default to 60-minute TTL |
| Scoped permissions | Tokens inherit a specific ACL rule set |
| Self-expiring | Compromised tokens stop working quickly |
How it works
Get your secret key
- Open your CloudSignal dashboard
- Go to Dashboard -> API Keys
- Create a new secret key (starts with
sk_). Legacycs_keys are still accepted. - 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);
}Related
- Next.js quickstart - Full implementation example
- Security overview - All security features