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:
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
Configure the relay
[ auth ]
key = "secret.jwk"
public = "demo" # Allow anonymous access to /demo prefix
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:
HS256 (default)
HS384
HS512
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:
RS256 (RSA + SHA256)
ES256 (ECDSA P-256)
EdDSA (Ed25519)
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
[ auth ]
# Path to signing key
key = "secret.jwk"
# Optional: Allow anonymous access to specific prefix
public = "demo"
Multiple Keys
For key rotation:
[ auth ]
keys = [
"secret-v1.jwk" ,
"secret-v2.jwk"
]
public = "demo"
Asymmetric Keys
When using asymmetric cryptography, the relay only needs the public key:
[ 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:
[ 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 )
use moq_native :: Client ;
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ;
let url = format! ( "https://relay.example.com/demo?jwt={}" , token );
let client = Client :: connect ( & url ) . await ? ;
# Read token from file
moq-cli publish \
--url "https://relay.example.com/demo?jwt=$( cat token.jwt)" \
--name my-stream \
fmp4
Token Generation Service
For production, generate tokens on your backend:
TypeScript (Node/Bun)
Rust
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 }
use moq_token :: { Key , Claims };
use std :: time :: { SystemTime , Duration };
// Load key
let key = Key :: from_file ( "secret.jwk" ) ? ;
// Create claims
let claims = Claims {
root : "demo" . into (),
publish : vec! [ "my-stream" . into ()],
subscribe : vec! [],
expires : Some ( SystemTime :: now () + Duration :: from_secs ( 3600 )),
cluster : false ,
issued : Some ( SystemTime :: now ()),
};
// Sign token
let token = key . encode ( & claims ) ? ;
// Return to client
Ok ( 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
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
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
[ 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
Token not accepted by relay
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