close
Skip to content

Authentication

moq-relay uses JWT (JSON Web Tokens) for authentication and authorization. Tokens control who can publish or subscribe to which paths.

Overview

There are two authentication modes:

Single Key (--auth-key)

A single JWK file used to verify all tokens. No kid header is required in JWTs. Good for development and simple deployments.

Key Directory (--auth-key-dir)

For production use with key rotation. Keys are resolved on demand by extracting the kid from the JWT header and fetching the corresponding key file.

  1. Generate signing keys (a random key ID is assigned automatically)
  2. Store each key as {kid}.jwk in a directory or serve via HTTP
  3. Configure the relay with the key directory or URL
  4. Issue tokens to clients with their allowed paths
  5. Clients connect with ?jwt=<token> query parameter

Quick Start

Generate a Key

Using the Rust CLI:

bash
# Symmetric key (simpler, key must stay secret)
moq-token-cli generate --out my-key.jwk

# Save to a directory as {kid}.jwk
moq-token-cli generate --out-dir ./keys/

# Asymmetric key (private signs, public verifies)
moq-token-cli generate --algorithm ES256 --out private.jwk --public public.jwk

# Asymmetric key, both saved to directories as {kid}.jwk
moq-token-cli generate --algorithm ES256 --out-dir ./private/ --public-dir ./keys/

A random key ID is generated if --id is not specified.

Configure the Relay

Single key (simplest):

toml
[auth]
key = "my-key.jwk"

Key directory (for key rotation):

toml
[auth]
# Point to the public keys directory (from --public-dir).
# For asymmetric algorithms, the relay only needs public keys to verify tokens.
key_dir = "/etc/moq/keys/"

Remote key server:

toml
[auth]
key_dir = "https://api.example.com/keys"

Issue a Token

bash
# Allow publishing to demo/my-stream and subscribing to anything under demo/
moq-token-cli sign --key my-key.jwk --root demo --publish my-stream --subscribe ""

The client connects with the token. The connection path can be the root or any parent:

text
# Connect at the token's root
https://relay.example.com/demo?jwt=eyJhbGciOiJIUzI1NiIs...

# Connect at the server root (permissions still scoped to demo/)
https://relay.example.com/?jwt=eyJhbGciOiJIUzI1NiIs...

Key Resolution

Single Key Mode (--auth-key)

The relay uses the specified key file to verify all incoming JWTs. No kid header is required in the token.

Key Directory Mode (--auth-key-dir)

Key files are stored as JSON by default. Legacy base64url-encoded files are also supported for backwards compatibility. Use --base64 when generating keys if you prefer the base64url format.

When a client connects with a JWT, the relay:

  1. Decodes the JWT header to extract the kid (key ID)
  2. Looks up the key from the configured source: {dir}/{kid}.jwk or {url}/{kid}.jwk
  3. Verifies the JWT signature with the resolved key
  4. Checks the token's permissions cover the connection path

Key IDs must contain only alphanumeric characters, hyphens, and underscores.

Token Claims

The JWT payload contains these claims:

ClaimDescription
rootBase path for publish/subscribe permissions
pubSuffix appended to root for publish permission
subSuffix appended to root for subscribe permission
expExpiration time (Unix timestamp)
iatIssued-at time (Unix timestamp)

Path Matching

The root claim sets a base path. The pub and sub claims are suffixes:

text
Full publish path = root + "/" + pub
Full subscribe path = root + "/" + sub

An empty suffix ("") allows access to anything under the root.

Examples:

rootpubsubCan publishCan subscribe
demomy-stream""demo/my-streamdemo/*
rooms/123alice""rooms/123/alicerooms/123/*
""""""EverythingEverything

Connection Path

The client's connection URL path does not need to match the token's root exactly. The connection path determines the scope of the session — all publish/subscribe operations are relative to it.

  • If the connection path extends the root (e.g., token root=demo, connect to /demo/room), permissions are narrowed to only paths under /demo/room.
  • If the connection path is a parent of the root (e.g., token root=demo, connect to /), permissions still apply but are scoped to the token's root. You can only access paths under demo/.
  • If the connection path is unrelated to the root (e.g., token root=demo, connect to /other), the connection is rejected.

The connection is also rejected if the resulting permissions are empty (no publish or subscribe paths remain after scoping).

Supported Algorithms

Symmetric (HMAC)

The same key signs and verifies. Simpler setup, but the key must be kept secret everywhere it's used.

  • HS256 - HMAC with SHA-256 (default)
  • HS384 - HMAC with SHA-384
  • HS512 - HMAC with SHA-512

Asymmetric (RSA/ECDSA)

Private key signs, public key verifies. The relay only needs the public key, so compromise of the relay doesn't leak signing capability.

  • RS256, RS384, RS512 - RSA PKCS#1 v1.5
  • PS256, PS384, PS512 - RSA PSS
  • ES256, ES384 - ECDSA
  • EdDSA - Edwards-curve DSA

Anonymous Access

The public setting allows unauthenticated access to a path prefix:

toml
[auth]
key = "my-key.jwk"
public = "anon"  # Anyone can publish/subscribe to anon/*

Set public = "" to make everything public (development only).

mTLS Peer Authentication

In addition to JWT auth, the relay can authenticate peers via mutual TLS. When the server is configured with a trusted root CA, any client that presents a certificate chaining to that CA is granted full access: root-scoped publish and subscribe permissions plus cluster privileges — equivalent to a JWT with publish: "", subscribe: "", and cluster: true.

This is primarily intended for relay-to-relay (clustering) authentication, as a simpler alternative to distributing long-lived JWTs.

Client certificate presentation is optional: connections without a certificate fall through to the normal JWT path unchanged.

toml
[tls]
cert = ["/etc/moq/server.pem"]
key  = ["/etc/moq/server.key"]
# One or more PEM files containing the CAs trusted to sign peer certificates.
root = ["/etc/moq/peer-ca.pem"]

The peer's cluster node name is taken from the first DNS SAN on its leaf certificate, so node identity is cryptographically bound to the cert. Certificates without a DNS SAN are still accepted but will not register as cluster nodes.

Only the quinn QUIC backend supports mTLS; configuring tls.root with any other backend is a startup error.

Example Configurations

See the demo/relay/ directory for complete working configuration files, including authentication setup:

Library Usage

Rust

TypeScript

See js/token/examples/sign-and-verify.ts for a complete working example of signing and verifying tokens.

See Also