Skip to content

Authentication

Authentication and Authorization

Introduction

This section explains how to authenticate using API keys and JWT bearer tokens. Protected routes require:

  1. X-API-Key on /api/v2/auth/ to obtain a JWT.
  2. Authorization: Bearer <token> on subsequent requests.
  3. Authorization: Bearer <token> on /api/v2/auth/refresh/ to refresh an existing JWT.

Authentication Flow

  1. API Key Validation: Clients must include their API key in the request headers.
  2. JWT Generation: The server returns a JWT on successful validation.
  3. Token Usage: Include the JWT in the Authorization header for protected endpoints.
  4. Token Refresh: Refresh long-lived sessions with /api/v2/auth/refresh/ before the current JWT expires.

Getting Started

API Key

Each client is provided with a key to send in the X-API-Key header.

Client ID

The JWT payload contains a generated client ID and access level.

Endpoints

Retrieve JWT

Request

URL: /api/v2/auth/

Method: GET

Headers: - X-API-Key: Your unique API key

API Response:

{ "client_id": "your_client_id", "access_level": "ADMIN", "token": "<jwt>" }

Example using requests in Python

import requests

# Define the URL and the endpoint
url = "https://<ROBOT_IP>/rest/api/v2/auth/"

# Define the headers with the API key
headers = {
    "X-API-Key": "your_api_key_here"
}

# Make the GET request
response = requests.get(url, headers=headers)

# Print the response
print(response.json())

# Get the JWT token from the response headers
jwt_token = response.headers.get("Authorization").split()[1]
print(f"JWT Token: {jwt_token}")

Example using curl

curl -X GET "https://<ROBOT_IP>/rest/api/v2/auth/" -H "X-API-Key: your_api_key_here"

Refresh JWT

Use this endpoint when a client already has a valid JWT and needs a replacement token without sending the API key again.

Request

URL: /api/v2/auth/refresh/

Method: GET

Headers: - Authorization: Bearer <JWT_TOKEN>

API Response:

{ "client_id": "your_client_id", "access_level": "ADMIN" }

The refreshed JWT is returned in the Authorization response header. Clients should replace their stored token with that value.

Example using requests in Python

import requests

url = "https://<ROBOT_IP>/rest/api/v2/auth/refresh/"
headers = {
    "Authorization": f"Bearer {jwt_token}"
}

response = requests.get(url, headers=headers)
response.raise_for_status()

jwt_token = response.headers.get("Authorization").split()[1]
print(response.json())

Example using curl

curl -i -X GET "https://<ROBOT_IP>/rest/api/v2/auth/refresh/" \
-H "Authorization: Bearer <JWT_TOKEN>"

Using the JWT Token for Subsequent Requests

After obtaining the JWT token, include it in the Authorization header for all subsequent requests.

Example using POST method and requests in Python

import requests
import jwt

# Define the URL and the endpoint
url = "https://<ROBOT_IP>/rest/api/v2/commands/go_to_relative/"

# Define the headers with the JWT token
headers = {
    "Authorization": f"Bearer {jwt_token}"
}

# Define the payload/data to send with the request
data = {"value": {"x": 1.5, "y": -0.5, "z": 0.0, "theta": 3.14}}

# Make the POST request
response = requests.post(url, json=data, headers=headers)

# Print the response
print(response.json())

Example using POST method with curl

curl -X POST "https://<ROBOT_IP>/rest/api/v2/commands/go_to_relative/" \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"value": {"x": 1.5, "y": -0.5, "z": 0.0, "theta": 3.14}}'

Security Best Practices

  1. Keep Your API Key Secure: Never share your API key publicly. Treat it as sensitive information.
  2. Token Expiration: JWT tokens expire after 30 minutes. Refresh existing sessions by calling /api/v2/auth/refresh/ before the JWT exp time.
  3. Token Storage: Store the JWT token securely in your client application. Avoid storing it in locations that are easily accessible, such as local storage or cookies.

For long-running WebSocket or video clients, decode the JWT payload locally and schedule refresh from the exp claim. The bundled Wizard frontend and pymirokai client do this automatically and use a fallback retry delay if a token cannot be decoded.

Conclusion

Following this flow ensures authenticated and authorized access to protected API routes.

MediaMTX / Wizard-Issued JWT

Wizard only accepts locally minted JWTs for its own REST API. It also exposes the public key for those JWTs as JWKS so MediaMTX can validate video-stream tokens locally.

When Wizard mints a local JWT through create_jwt(), the payload builder injects a mediamtx_permissions claim by default:

[{"action":"read","path":""}]

That default permission is enough for RTSP and WHEP viewers. Native RTSP clients pass the token as ?jwt=<token> on the stream URL; pymirokai does this automatically through its VideoStreamManager.

MediaMTX publisher tokens are requested explicitly with mediamtx_publish=true and require an elevated API key for TCP requests:

curl -H "X-API-Key: <admin-or-enchanter-key>" \
"http://<host>:8000/api/v2/auth/?username=mediamtx-publisher&mediamtx_publish=true"

Wizard signs local JWTs with ES256 and exposes the matching public key as JWKS. By default, the private key is persisted at ~/.wizard/jwt_es256_private_key.pem so MediaMTX can keep validating tokens across Wizard restarts. Use WIZARD_JWT_SIGNING_KEY_PATH to choose another key path. Use WIZARD_JWT_EPHEMERAL=1 for an in-memory key that changes on each process start.

Wizard then exposes its JWKS at:

  • Direct REST server: http://<host>:8000/.well-known/jwks.json
  • Through the public web proxy: https://<host>/rest/.well-known/jwks.json

Configure MediaMTX on the same machine to use that local JWKS:

authMethod: jwt
authJWTJWKS: http://127.0.0.1:8000/.well-known/jwks.json
authJWTClaimKey: mediamtx_permissions

In the simulation Docker network, configure MediaMTX with http://wizard_web_server:8000/.well-known/jwks.json. Do not use 127.0.0.1 there because it would point at the MediaMTX container itself.

The head on-demand publishers request a publish token before starting their GStreamer pipeline, then publish to the local head MediaMTX with the token in the RTSP jwt query parameter. When the public Layer4 Caddy proxy is enabled, clients should use the canonical external form rtsps://<robot-or-base>:8555/head_color?jwt=<token>, while internal MediaMTX hops can stay on plain rtsp://...:8554.

Wizard logs MediaMTX publisher-token issuance when mediamtx_publish=true. JWKS requests are logged with rate limiting and can only identify the requester IP; they cannot include the viewer client id because MediaMTX fetches JWKS without forwarding the viewer JWT.