Skip to main content

Authentication

Moq uses JWT (JSON Web Token) based authentication to control who can publish and subscribe to broadcasts. This guide covers how to set up and use authentication in your Moq deployment.

Overview

Moq’s authentication system provides:
  • Path-based authorization: Control access to specific broadcast paths
  • Publish/Subscribe permissions: Separate permissions for publishers and subscribers
  • Token-based: No server-side session management needed
  • Flexible key algorithms: Support for both symmetric (HMAC) and asymmetric (RSA, ECDSA, EdDSA) keys
  • Anonymous access: Optional public paths that don’t require authentication

Quick Start

Generate a key and create tokens in three steps:
1

Generate a signing key

# Install moq-token CLI
cargo install --git https://github.com/moq-dev/moq moq-token-cli

# Generate a symmetric key (HMAC-SHA256)
moq-token --key secret.jwk generate
2

Configure the relay

relay.toml
[auth]
key = "secret.jwk"
public = "demo"  # Allow anonymous access to /demo prefix
3

Generate tokens for clients

# Token for publishing to demo/my-stream
moq-token --key secret.jwk sign \
  --root "demo" \
  --publish "my-stream" \
  > publisher.jwt

# Token for subscribing to demo/*
moq-token --key secret.jwk sign \
  --root "demo" \
  --subscribe "" \
  > subscriber.jwt

Authentication Flow

Here’s how authentication works in Moq:
1. Client generates/receives JWT token
2. Client connects to relay with token in URL: ?jwt=<token>
3. Relay validates token signature
4. Relay checks permissions for requested operation
5. If valid, connection proceeds; otherwise, rejected
Tokens are passed via query parameters in the WebTransport URL, not as headers. This is because WebTransport doesn’t support custom headers.

Key Generation

moq-token supports multiple cryptographic algorithms:

Symmetric Keys (HMAC)

Recommended for simple deployments where the relay is the only verifier:
moq-token --key secret.jwk generate --algorithm HS256
Pros:
  • Simple setup (one key)
  • Fast signing and verification
  • Smaller key size
Cons:
  • Same key used for signing and verification
  • Must keep key secret from clients
  • Can’t delegate token generation safely

Asymmetric Keys (RSA, ECDSA, EdDSA)

Recommended for distributed systems where token generation is separate from verification:
moq-token --key private.jwk generate --algorithm RS256 --public public.jwk
Pros:
  • Public key can be shared openly
  • Private key only needed for signing
  • Can delegate signing to auth service
  • Multiple relays can verify with same public key
Cons:
  • More complex setup (two keys)
  • Slightly slower operations
  • Larger key size
For production deployments with multiple relays, use ES256 (ECDSA) for a good balance of security, performance, and key size.

Key Rotation

Support multiple keys with key IDs:
# Generate keys with IDs
moq-token --key secret-v1.jwk generate --algorithm HS256 --id "v1"
moq-token --key secret-v2.jwk generate --algorithm HS256 --id "v2"

# New tokens use v2, but relay accepts both
Relay configuration:
[auth]
keys = ["secret-v1.jwk", "secret-v2.jwk"]

Token Claims

JWT tokens contain claims that define permissions:

Root Path

The base path for all operations:
moq-token --key secret.jwk sign --root "demo"
  • Client connects to /demo on the relay
  • All broadcast paths are relative to this root
  • Empty string ("") means root of the relay

Publish Permissions

Control which broadcasts the client can publish:
# Publish to demo/my-stream only
moq-token --key secret.jwk sign \
  --root "demo" \
  --publish "my-stream"

# Publish to any demo/streams/* path
moq-token --key secret.jwk sign \
  --root "demo" \
  --publish "streams"

# Publish to anything under demo/
moq-token --key secret.jwk sign \
  --root "demo" \
  --publish ""

# No publishing allowed (omit --publish)
moq-token --key secret.jwk sign \
  --root "demo"

Subscribe Permissions

Control which broadcasts the client can subscribe to:
# Subscribe to demo/my-stream only
moq-token --key secret.jwk sign \
  --root "demo" \
  --subscribe "my-stream"

# Subscribe to any demo/streams/* path
moq-token --key secret.jwk sign \
  --root "demo" \
  --subscribe "streams"

# Subscribe to anything under demo/
moq-token --key secret.jwk sign \
  --root "demo" \
  --subscribe ""

# No subscribing allowed (omit --subscribe)
moq-token --key secret.jwk sign \
  --root "demo"

Multiple Permissions

You can specify multiple paths for each permission type:
moq-token --key secret.jwk sign \
  --root "demo" \
  --publish "my-stream" \
  --publish "my-other-stream" \
  --subscribe "streams" \
  --subscribe "archives"

Token Expiration

Set an expiration time for tokens:
# Expires in 1 hour (3600 seconds from now)
moq-token --key secret.jwk sign \
  --root "demo" \
  --publish "my-stream" \
  --expires $(date -d '+1 hour' +%s)

# Expires at specific timestamp
moq-token --key secret.jwk sign \
  --root "demo" \
  --publish "my-stream" \
  --expires 1735689600  # Jan 1, 2025
Always set expiration times for tokens in production. Tokens without expiration are valid forever!

Cluster Tokens

Special tokens for relay-to-relay communication:
moq-token --key secret.jwk sign \
  --root "" \
  --publish "" \
  --subscribe "" \
  --cluster
The --cluster flag marks this as a cluster node, which changes announcement behavior.

Relay Configuration

Basic Authentication

relay.toml
[auth]
# Path to signing key
key = "secret.jwk"

# Optional: Allow anonymous access to specific prefix
public = "demo"

Multiple Keys

For key rotation:
relay.toml
[auth]
keys = [
  "secret-v1.jwk",
  "secret-v2.jwk"
]
public = "demo"

Asymmetric Keys

When using asymmetric cryptography, the relay only needs the public key:
relay.toml
[auth]
key = "public.jwk"  # Public key for verification
public = "demo"
Keep private.jwk on your auth server, not the relay.

Anonymous Access

Allow unauthenticated access to specific paths:
relay.toml
[auth]
key = "secret.jwk"
public = "demo"  # Anyone can access /demo without a token
With this config:
  • /demo/* - No token required
  • /private/* - Token required
  • / - Token required

Using Tokens

In URLs

Pass tokens via query parameter:
import { Client } from '@moq/lite'

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
const url = `https://relay.example.com/demo?jwt=${token}`

const client = await Client.connect(url)

Token Generation Service

For production, generate tokens on your backend:
import { Key, Claims } from '@moq/token'

// Load key
const key = await Key.fromFile('secret.jwk')

// Create claims
const claims: Claims = {
  root: 'demo',
  publish: ['my-stream'],
  subscribe: [],
  expires: Date.now() + 3600000, // 1 hour
  cluster: false
}

// Sign token
const token = await key.encode(claims)

// Return to client
return { token }

Token Verification

Verify a token’s contents without connecting:
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | \
moq-token --key secret.jwk verify
Output:
Claims {
    root: "demo",
    publish: ["my-stream"],
    subscribe: [],
    expires: Some(SystemTime { ... }),
    cluster: false,
    issued: Some(SystemTime { ... }),
}

Security Best Practices

Use HTTPS/TLS

Always use TLS for production to prevent token interception

Short Expiration

Use short-lived tokens (minutes to hours, not days)

Principle of Least Privilege

Grant only the minimum permissions needed

Rotate Keys

Periodically rotate signing keys using key IDs

Secure Key Storage

Never commit keys to version control; use secrets management

Validate Server-Side

Generate tokens on your backend, not in client code

Never Do This

// ❌ DON'T: Embed signing key in client code
const key = 'secret-key-12345'
const token = signToken(key, claims) // Client can generate any token!

// ✅ DO: Request token from your backend
const response = await fetch('/api/token', {
  method: 'POST',
  body: JSON.stringify({ userId, action: 'publish' })
})
const { token } = await response.json()

Example: Full Auth Flow

A complete example of authentication in a real application:

1. Backend: Token Generation

backend.ts
import { Key, Claims } from '@moq/token'
import express from 'express'

const app = express()
const key = await Key.fromFile('secret.jwk')

app.post('/api/moq-token', async (req, res) => {
  // Authenticate user (your existing auth)
  const user = await authenticateUser(req)
  if (!user) return res.status(401).send('Unauthorized')

  // Determine permissions based on user
  const claims: Claims = {
    root: 'streams',
    publish: [`user-${user.id}`], // Can only publish to own stream
    subscribe: [''], // Can subscribe to all streams
    expires: Date.now() + 3600000, // 1 hour
    cluster: false
  }

  // Sign and return token
  const token = await key.encode(claims)
  res.json({ token, relayUrl: 'https://relay.example.com' })
})

2. Frontend: Request Token

frontend.ts
import { Client } from '@moq/lite'
import { Publisher } from '@moq/publish'

// Get token from backend
const response = await fetch('/api/moq-token', {
  method: 'POST',
  credentials: 'include' // Send cookies for auth
})
const { token, relayUrl } = await response.json()

// Connect to relay with token
const url = `${relayUrl}/streams?jwt=${token}`
const client = await Client.connect(url)

// Publish stream
const publisher = new Publisher(client, `user-${userId}`)
await publisher.publish(stream)

3. Relay Configuration

relay.toml
[server]
listen = "[::]:443"

[server.tls]
cert = "cert.pem"
key = "key.pem"

[auth]
key = "secret.jwk"
# No public paths - all streams require auth

Troubleshooting

  • Verify you’re using the correct key file
  • For asymmetric keys, ensure relay has the public key
  • Check key ID matches if using multiple keys
  • Verify key hasn’t been rotated/changed
  • Check token claims with moq-token verify
  • Verify root path matches connection URL
  • Ensure publish or subscribe arrays include the requested path
  • Check for leading/trailing slashes (should be omitted)
  • Check system clock is synchronized (NTP)
  • Verify --expires timestamp is in the future
  • Consider clock skew tolerance (~5 minutes)
  • Use shorter token lifetimes and refresh
  • Verify relay has [auth] section configured
  • Check relay logs for specific error
  • Ensure token is passed as ?jwt= query parameter
  • Test with moq-token verify first

Next Steps

Relay Setup

Configure your relay server

Production

Production deployment guide

Publishing

Publish authenticated streams

Watching

Subscribe with authentication

Build docs developers (and LLMs) love