Security
Authentication and production security.
This page covers authentication options for securing your Resonate Server and SDK connections.
Overview#
Resonate uses JWT token-based authentication for securing server and SDK connections.
Authentication is optional by default but strongly recommended for production deployments.
SDK Support Matrix#
| Authentication Method | Server | TypeScript SDK | Rust SDK | Python SDK |
|---|---|---|---|---|
| Token-based (JWT) | ✅ | ✅ | ✅ | ❌ (planned) |
The Python SDK does not yet support token-based authentication. Token auth support is planned for a future release. The Python SDK currently works with the legacy Resonate server only.
Token-based authentication#
Token-based authentication uses JWT bearer tokens signed with Ed25519 keys. The server validates tokens using a public key; clients sign tokens using the corresponding private key.
Generate keys#
Generate an Ed25519 key pair:
openssl genpkey -algorithm Ed25519 -out private.pem
openssl pkey -in private.pem -pubout -out public.pemServer configuration#
Start the server with the public key:
resonate serve --auth-publickey public.pemOr set it in resonate.toml:
[auth]
publickey = "/etc/resonate/auth/public.pem"
# Optional: enforce expected claims
# iss = "your-issuer"
# aud = "your-audience"When auth is enabled, all API requests must include a valid JWT token in the Authorization: Bearer <token> header.
TypeScript SDK#
import { Resonate } from "@resonatehq/sdk";
const resonate = new Resonate({
url: "http://localhost:8001",
token: process.env.RESONATE_TOKEN,
});The SDK sends the token as a Bearer token in the Authorization header automatically.
Example: Secure production setup#
import { Resonate } from "@resonatehq/sdk";
const token = process.env.RESONATE_TOKEN;
if (!token) {
throw new Error("RESONATE_TOKEN environment variable is required");
}
const resonate = new Resonate({
url: process.env.RESONATE_URL || "http://localhost:8001",
token: token,
});
export default resonate;Rust SDK#
use resonate::prelude::*;
let resonate = Resonate::new(ResonateConfig {
token: std::env::var("RESONATE_TOKEN").ok(),
url: Some("http://localhost:8001".into()),
..Default::default()
});ResonateConfig::token is Option<String>, so std::env::var("RESONATE_TOKEN").ok() is the idiomatic way to populate it from the environment. Resonate::new returns Resonate directly (not a Result).
CORS (Cross-Origin Resource Sharing)#
Browser-based clients — SDK applications running in a tab, internal dashboards, agent UIs — need CORS headers to call the Resonate Server from a different origin. CORS is disabled by default; the server only emits CORS headers when you configure one or more allowed origins.
Why CORS matters#
If your server is at https://resonate.yourcompany.com and your client runs at https://app.example.com, the browser will block the request unless the server returns the appropriate Access-Control-Allow-Origin headers. CORS configuration covers the HTTP API and the long-poll/SSE delivery endpoint together — both are served from the same router.
Server-to-server traffic (workers calling the API from a backend process) is not affected by CORS. Configure it only if a browser is in the request path.
Server configuration#
Configure allowed origins in resonate.toml:
[server.cors]
allow_origins = ["https://app.example.com"]Or via environment variable:
RESONATE_SERVER__CORS__ALLOW_ORIGINS=https://app.example.comOr via repeatable CLI flag:
resonate serve \
--server-cors-allow-origin https://app.example.com \
--server-cors-allow-origin https://admin.example.comBehavior#
| Configuration | Result |
|---|---|
allow_origins empty (default) | No CORS headers emitted; browser cross-origin requests are blocked. |
allow_origins = ["https://app.example.com", ...] | Server reflects the request Origin header only when it matches an entry in the list. |
allow_origins = ["*"] | Permissive mode: any origin is accepted, and Access-Control-Allow-Credentials: true is also set. |
Allowed methods: GET, POST, PUT, PATCH, DELETE, OPTIONS.
Allowed request headers: Origin, Content-Length, Content-Type, Authorization.
Preflight OPTIONS requests are handled by the server automatically — no additional configuration required.
Setting allow_origins = ["*"] enables permissive mode, which also sends Access-Control-Allow-Credentials: true. This combination is broadly accepting and should generally be reserved for development. For production, list each trusted origin explicitly.
Example: production with explicit origins#
[auth]
publickey = "/etc/resonate/auth/public.pem"
[server.cors]
allow_origins = [
"https://app.example.com",
"https://admin.example.com",
]Combined with JWT authentication, this restricts the server to a known set of browser origins while still requiring a valid token on every request.
CORS support was introduced in Resonate Server v0.9.4.
Outbound HTTP push authentication#
Inbound JWT auth (above) protects the server from unauthenticated SDK clients. The matching server-side concern is outbound authentication: when the server pushes a task to a worker URL via HTTP push, the worker may itself sit behind an authenticated endpoint (for example a GCP Cloud Run service deployed with --no-allow-unauthenticated, or any internal service that validates a bearer token).
v0.9.5 introduced three outbound auth modes for the HTTP push transport. All three apply only to the http:// / https:// push transport — they do not affect Pub/Sub or bash exec deliveries.
| Mode | What the server sends |
|---|---|
none (default) | No Authorization header. Suitable for unauthenticated workers or networks with trust enforced at another layer. |
bearer | A static Authorization: Bearer <token> header on every delivery. The token is configured once and never refreshed. |
gcp | A GCP-issued OIDC ID token, minted per-audience using Application Default Credentials. Suitable for Cloud Run, Cloud Functions, and other GCP services that natively validate Google-signed ID tokens. |
The mode is set with --transports-http-push-auth-mode, the matching [transports.http_push.auth].mode TOML key, or RESONATE_TRANSPORTS__HTTP_PUSH__AUTH__MODE.
Static bearer token#
Use this mode when every worker shares a single secret you control end-to-end (an internal service mesh, a self-hosted deployment with a shared HMAC).
[transports.http_push.auth]
mode = "bearer"
# Token is typically supplied by env var rather than checked-in config:
# RESONATE_TRANSPORTS__HTTP_PUSH__AUTH__TOKEN=<token>resonate serve \
--transports-http-push-auth-mode bearer \
--transports-http-push-auth-token "$RESONATE_PUSH_TOKEN"The token is sent verbatim as Authorization: Bearer <token> on every push. It is never rotated by the server — rotation is your responsibility.
GCP OIDC ID token#
Use this mode when workers run on GCP and validate Google-signed ID tokens (Cloud Run, Cloud Functions, IAP-protected services, etc.). The server mints an ID token per audience using the GCP credentials available to the server process (Application Default Credentials).
[transports.http_push.auth]
mode = "gcp"
# Optional: override the audience used when minting tokens. When absent,
# each delivery target URL is used as its own audience.
# audience = "https://workers.example.com"resonate serve --transports-http-push-auth-mode gcpTwo notes on audience handling:
- Default — if you do not set
audience, the server uses each delivery target URL verbatim as the audience. This is the right setting for Cloud Run services where each worker validates tokens against its own URL. - Override — set
audience(or--transports-http-push-auth-aud) when every worker validates against a single shared audience (for example an API gateway in front of multiple workers).
Tokens are cached per audience inside the server process, so audience reuse across deliveries does not re-mint on every call.
If the GCP credentials provider fails to mint a token (missing ADC, network failure, IAM misconfiguration), the server logs a warning and delivers the request without an Authorization header. The delivery is not retried at the auth layer; the worker will reject the unauthenticated request and the standard task-retry path applies. Watch server logs for OIDC ID token mint failed to detect this state.
Custom header name#
By default the token is sent in the Authorization header. Override with --transports-http-push-auth-header (or the matching TOML / env key) when the worker expects a different header — for example X-Goog-IAP-JWT-Assertion for some IAP setups:
resonate serve \
--transports-http-push-auth-mode gcp \
--transports-http-push-auth-header X-Goog-IAP-JWT-AssertionQuick reference#
| TOML key | CLI flag | Env var |
|---|---|---|
[transports.http_push.auth].mode | --transports-http-push-auth-mode | RESONATE_TRANSPORTS__HTTP_PUSH__AUTH__MODE |
[transports.http_push.auth].token | --transports-http-push-auth-token | RESONATE_TRANSPORTS__HTTP_PUSH__AUTH__TOKEN |
[transports.http_push.auth].audience | --transports-http-push-auth-aud | RESONATE_TRANSPORTS__HTTP_PUSH__AUTH__AUDIENCE |
[transports.http_push.auth].header | --transports-http-push-auth-header | RESONATE_TRANSPORTS__HTTP_PUSH__AUTH__HEADER |
Production best practices#
Token management#
- Generate strong tokens — Use properly signed JWTs with appropriate claims and expiry
- Store securely — Use environment variables, AWS Secrets Manager, Google Secret Manager, etc.
- Rotate regularly — Implement key rotation policies
- Scope appropriately — Use different keys for different environments (dev, staging, prod)
Network security#
Even with authentication enabled, follow network security best practices:
- Restrict network access with firewalls and security groups
- Use private networks when possible (VPC, private subnets)
- Enable rate limiting at the load balancer or API gateway level
Monitoring#
Monitor authentication failures and suspicious activity:
- Enable server logging with
--level debugto see auth events - Set up alerts for repeated auth failures
- Track token usage patterns
See the Observability documentation for logging configuration.
Multi-environment setup#
Use different credentials for each environment:
RESONATE_URL=http://localhost:8001
RESONATE_TOKEN=dev-token-hereRESONATE_URL=https://resonate.yourcompany.com
RESONATE_TOKEN=prod-token-from-secrets-managerTroubleshooting#
"401 Unauthorized" errors#
Symptom: SDK requests fail with 401 status code
Common causes:
- Token not provided
- Token signature doesn't match the server's public key
- Token expired
- Environment variable not loaded correctly
Solution:
- Verify the server was started with
--auth-publickeypointing to the correct public key - Verify the SDK token was signed with the matching private key
- Check token expiry claims
- Review server logs (
--level debug) for auth failure details
Examples and tutorials#
For a working example of authenticated applications:
- TypeScript: See the token authentication example repository
Next steps#
- Deploy to production with authentication enabled
- Set up observability to monitor auth events
- Configure message transports for distributed workers
- Review server configuration for all server flags
Related documentation#
- Develop with TypeScript SDK — SDK reference with auth examples
- Develop with Rust SDK — SDK reference with auth examples
- Server configuration — Complete server flag reference