CloudSignal Docs
GuidesAuth Providers

Clerk integration

Configure Clerk as an authentication provider for CloudSignal MQTT.

Configure Clerk as an authentication provider so your Clerk users can connect to CloudSignal MQTT with credentials derived from their Clerk session token. Clerk supports both development and production environments with different domains.

Prerequisites

  • A Clerk application with authentication enabled
  • A CloudSignal account with API keys
  • Your Clerk Frontend API domains (development and/or production)

Understanding Clerk environments

Clerk uses different domains for development and production:

EnvironmentDomain formatExample
Development{instance}.clerk.accounts.devhappy-fox-12.clerk.accounts.dev
ProductionCustom domainclerk.your-domain.com

You can configure both environments simultaneously. CloudSignal will automatically match incoming tokens to the correct environment based on the JWT's issuer claim.

Required: configure custom claims

Important: Clerk's default JWT does not include the user's email address. You must configure custom claims for CloudSignal to identify users.

CloudSignal requires the user's email to create MQTT credentials. Configure custom claims in Clerk:

  1. Go to your Clerk Dashboard
  2. Navigate to SessionsCustomize session token
  3. Add the following claims:
{
  "email": "{{user.primary_email_address}}",
  "user_id": "{{user.id}}",
  "name": "{{user.first_name}} {{user.last_name}}"
}
  1. Click Save

Alternative: use a JWT template

If you prefer to use JWT templates (for API-specific tokens):

  1. Go to JWT TemplatesCreate template
  2. Add the same claims as above
  3. Use getToken({ template: 'your-template-name' }) when requesting tokens

Configuration

Find your Clerk frontend API domains

Development domain

  1. Go to your Clerk Dashboard
  2. Select your application
  3. Navigate to API Keys
  4. Find your Publishable Key (starts with pk_test_)
  5. The domain is encoded in the key. You can also find it in your Clerk Dashboard URL

Alternatively, decode from your publishable key:

// pk_test_ keys contain the Frontend API URL in base64
const pk = 'pk_test_aGFwcHktZm94LTEyLmNsZXJrLmFjY291bnRzLmRldiQ=';
const domain = atob(pk.replace('pk_test_', '').replace('$', ''));
// Result: happy-fox-12.clerk.accounts.dev

Production domain

For production, you'll have either:

  • A Clerk-provided domain from your pk_live_ key
  • A custom domain you've configured (for example, clerk.your-domain.com)

Add Clerk provider in CloudSignal

  1. Go to CloudSignal Dashboard
  2. Navigate to ConnectionsAuth Providers
  3. Click on Clerk
  4. Enter your configuration:
FieldValue
Development Frontend APIYour dev domain (for example, happy-fox-12.clerk.accounts.dev)
Production Frontend APIYour production domain (for example, clerk.your-domain.com)

At least one domain is required. Configure both if you want to support tokens from both environments.

  1. Click Test Connection to verify both environments
  2. Click Save Changes
  3. Enable the provider using the toggle

Configure CORS (for browser apps)

If exchanging tokens from browser-based applications:

  1. Go to SettingsSecurity
  2. Add your application's domain to Allowed Origins
  3. Save changes

Test the integration

Use the CloudSignal API to test token exchange:

curl -X POST https://api.cloudsignal.io/v2/tokens/exchange \
  -H "Content-Type: application/json" \
  -d '{
    "organization_id": "YOUR_ORG_ID",
    "token": "YOUR_CLERK_SESSION_TOKEN"
  }'

Implementation

React/Next.js example

import { useAuth } from '@clerk/nextjs';
import mqtt from 'mqtt';

// Exchange Clerk token for MQTT credentials
async function getMqttCredentials(sessionToken: string) {
  const response = await fetch('/api/mqtt-credentials', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ clerkToken: sessionToken })
  });

  return response.json();
}

// React component
function MqttConnection() {
  const { getToken } = useAuth();

  async function connect() {
    // Get Clerk session token
    const token = await getToken();

    if (!token) {
      throw new Error('User not authenticated');
    }

    // Exchange for MQTT credentials
    const credentials = await getMqttCredentials(token);

    // Connect to MQTT
    const client = mqtt.connect('wss://connect.cloudsignal.app:18885/', {
      username: credentials.mqtt_username,
      password: credentials.mqtt_password
    });

    client.on('connect', () => {
      console.log('Connected to MQTT');
    });

    return client;
  }

  return <button onClick={connect}>Connect to MQTT</button>;
}

API route (Next.js)

// app/api/mqtt-credentials/route.ts
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const { clerkToken } = await request.json();

  const response = await fetch('https://api.cloudsignal.io/v2/tokens/exchange', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      organization_id: process.env.CLOUDSIGNAL_ORG_ID,
      token: clerkToken
    })
  });

  const credentials = await response.json();
  return NextResponse.json(credentials);
}

Direct browser exchange (with CORS)

If you've configured CORS, you can exchange tokens directly from the browser:

import { useAuth } from '@clerk/nextjs';

async function exchangeTokenDirectly() {
  const { getToken } = useAuth();
  const token = await getToken();

  const response = await fetch('https://api.cloudsignal.io/v2/tokens/exchange', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      organization_id: 'YOUR_ORG_ID',
      token: token
    })
  });

  return response.json();
}

Handling token refresh

Clerk session tokens expire. Handle reconnection with fresh credentials:

import { useAuth } from '@clerk/nextjs';
import mqtt from 'mqtt';

let mqttClient: mqtt.MqttClient | null = null;

function useMqttConnection() {
  const { getToken } = useAuth();

  async function reconnect() {
    const token = await getToken();
    if (!token) return;

    const credentials = await getMqttCredentials(token);

    if (mqttClient) {
      mqttClient.end();
    }

    mqttClient = mqtt.connect('wss://connect.cloudsignal.app:18885/', {
      username: credentials.mqtt_username,
      password: credentials.mqtt_password
    });
  }

  // Reconnect when MQTT connection is lost
  useEffect(() => {
    if (mqttClient) {
      mqttClient.on('close', () => {
        // Wait a moment then reconnect with fresh credentials
        setTimeout(reconnect, 1000);
      });
    }
  }, [mqttClient]);

  return { reconnect, client: mqttClient };
}

ACL rules for Clerk users

Create ACL rules that match Clerk user identifiers:

# User can only access their own topics
User Pattern: %
Topic: users/%u/#
Permission: pubsub

The MQTT username derived from Clerk will be the user's email (sanitized).

Troubleshooting

"Token issuer does not match" error

This means the JWT's iss claim doesn't match your configured domains.

CheckWhat to verify
Token environmentDevelopment tokens have issuer like https://happy-fox-12.clerk.accounts.dev
Domain configurationEnsure you've configured the correct domain for that environment
TyposDomain must match exactly (no trailing slashes)

"Invalid token" error

CheckWhat to verify
Token freshnessClerk session tokens expire
Token sourceUse getToken() from Clerk, not the publishable key
JWKS endpointRun "Test Connection" in the dashboard

Test connection shows partial success

This means one environment connected but the other failed:

  • Check that both domains are correctly configured
  • Verify the failing domain is accessible
  • Ensure your Clerk application has the correct environment set up

CORS errors in browser

CheckWhat to verify
Allowed originsGo to Settings → Security → Allowed Origins
Exact originInclude protocol (for example, https://your-app.com)
Localhost developmentAdd http://localhost:3000 for local testing

Next steps

On this page