GuidesAuth Providers
Token exchange API
API reference for exchanging identity provider tokens for MQTT credentials.
Exchange an identity provider token (Supabase, Firebase, Auth0, Clerk) for temporary CloudSignal MQTT credentials. Use this endpoint from your backend whenever an authenticated user needs to connect to the broker.
Endpoint
POST https://api.cloudsignal.io/v2/tokens/exchangeAuthentication
Requires a CloudSignal API key (secret key recommended for server-side use):
Authorization: Bearer sk_live_your_secret_keyRequest
Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer token with your CloudSignal API key |
Content-Type | Yes | application/json |
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Identity provider name: supabase, firebase, or auth0 |
token | string | Yes | JWT/access token from the identity provider |
ttl | integer | No | Token TTL in seconds (default: 3600, max: 86400) |
Example request
curl -X POST https://api.cloudsignal.io/v2/tokens/exchange \
-H "Authorization: Bearer sk_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"provider": "supabase",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"ttl": 3600
}'Response
Success response (200)
{
"mqtt_username": "user_abc123@a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"mqtt_password": "temp_xyz789...",
"expires_at": "2024-01-15T13:00:00Z",
"expires_in": 3600,
"provider": "supabase",
"user_id": "abc123"
}| Field | Type | Description |
|---|---|---|
mqtt_username | string | Full MQTT username including organization ID |
mqtt_password | string | Temporary MQTT password |
expires_at | string | ISO 8601 timestamp when credentials expire |
expires_in | integer | Seconds until expiration |
provider | string | The identity provider used |
user_id | string | User identifier extracted from the token |
Error responses
400 Bad Request
{
"error": "invalid_request",
"message": "Missing required parameter: provider"
}401 Unauthorized
{
"error": "unauthorized",
"message": "Invalid or missing API key"
}422 Unprocessable Entity
{
"error": "invalid_token",
"message": "Token validation failed: token expired"
}| Error code | Description |
|---|---|
invalid_request | Missing or invalid parameters |
unauthorized | Invalid API key |
invalid_token | Token validation failed |
provider_not_found | Provider not configured |
provider_error | Error communicating with provider |
Code examples
JavaScript/TypeScript
interface TokenExchangeResponse {
mqtt_username: string;
mqtt_password: string;
expires_at: string;
expires_in: number;
provider: string;
user_id: string;
}
async function exchangeToken(
provider: 'supabase' | 'firebase' | 'auth0',
token: string,
ttl?: number
): Promise<TokenExchangeResponse> {
const response = await fetch('https://api.cloudsignal.io/v2/tokens/exchange', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CLOUDSIGNAL_SK}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
provider,
token,
...(ttl && { ttl })
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Token exchange failed: ${error.message}`);
}
return response.json();
}
// Usage
const credentials = await exchangeToken('supabase', supabaseAccessToken);
console.log(`Username: ${credentials.mqtt_username}`);
console.log(`Expires in: ${credentials.expires_in} seconds`);Python
import os
import requests
from dataclasses import dataclass
from datetime import datetime
@dataclass
class MqttCredentials:
mqtt_username: str
mqtt_password: str
expires_at: datetime
expires_in: int
provider: str
user_id: str
def exchange_token(
provider: str,
token: str,
ttl: int = None
) -> MqttCredentials:
url = 'https://api.cloudsignal.io/v2/tokens/exchange'
headers = {
'Authorization': f'Bearer {os.environ["CLOUDSIGNAL_SK"]}',
'Content-Type': 'application/json'
}
payload = {
'provider': provider,
'token': token
}
if ttl:
payload['ttl'] = ttl
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
return MqttCredentials(
mqtt_username=data['mqtt_username'],
mqtt_password=data['mqtt_password'],
expires_at=datetime.fromisoformat(data['expires_at'].replace('Z', '+00:00')),
expires_in=data['expires_in'],
provider=data['provider'],
user_id=data['user_id']
)
# Usage
credentials = exchange_token('firebase', firebase_id_token, ttl=7200)
print(f"Username: {credentials.mqtt_username}")
print(f"Expires at: {credentials.expires_at}")Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
type TokenExchangeRequest struct {
Provider string `json:"provider"`
Token string `json:"token"`
TTL int `json:"ttl,omitempty"`
}
type TokenExchangeResponse struct {
MqttUsername string `json:"mqtt_username"`
MqttPassword string `json:"mqtt_password"`
ExpiresAt time.Time `json:"expires_at"`
ExpiresIn int `json:"expires_in"`
Provider string `json:"provider"`
UserID string `json:"user_id"`
}
func ExchangeToken(provider, token string, ttl int) (*TokenExchangeResponse, error) {
reqBody := TokenExchangeRequest{
Provider: provider,
Token: token,
TTL: ttl,
}
jsonBody, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", "https://api.cloudsignal.io/v2/tokens/exchange", bytes.NewBuffer(jsonBody))
req.Header.Set("Authorization", "Bearer "+os.Getenv("CLOUDSIGNAL_SK"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result TokenExchangeResponse
json.NewDecoder(resp.Body).Decode(&result)
return &result, nil
}Best practices
Cache credentials
Don't call the API for every MQTT connection. Cache credentials and refresh before expiry:
class CredentialsManager {
private credentials: TokenExchangeResponse | null = null;
private refreshThreshold = 5 * 60 * 1000; // 5 minutes
async getCredentials(provider: string, token: string): Promise<TokenExchangeResponse> {
if (this.shouldRefresh()) {
this.credentials = await exchangeToken(provider, token);
}
return this.credentials!;
}
private shouldRefresh(): boolean {
if (!this.credentials) return true;
const expiresAt = new Date(this.credentials.expires_at).getTime();
return Date.now() > expiresAt - this.refreshThreshold;
}
}Handle errors gracefully
async function safeExchangeToken(provider: string, token: string) {
try {
return await exchangeToken(provider, token);
} catch (error) {
if (error.message.includes('token expired')) {
// Trigger re-authentication with identity provider
await refreshIdentityProviderToken();
return await exchangeToken(provider, getNewToken());
}
throw error;
}
}Use appropriate TTL
Choose TTL based on your use case:
| Use case | Recommended TTL | Notes |
|---|---|---|
| Web session | 3600 (1 hour) | Match browser session |
| Mobile app | 7200 (2 hours) | Reduce API calls |
| Short task | 300 (5 min) | Minimize exposure |
| Background job | 60 (1 min) | Task-scoped credentials |
Shorter TTLs are more secure but require more frequent token exchanges. Balance security with API usage.
Rate limits
| Plan | Requests/minute | Requests/day |
|---|---|---|
| Free | 10 | 1,000 |
| Pro | 60 | 10,000 |
| Enterprise | Custom | Custom |
Exceeding rate limits returns 429 Too Many Requests. Implement exponential backoff for retries.
Next steps
- Supabase integration - JWT secret-based exchange
- Firebase integration - Project ID-based exchange
- Auth0 integration - Domain + audience exchange
- API Keys guide - Manage CloudSignal secret keys