Skip to main content

resonate-token-authentication-typescript

Secure Resonate applications with JWT-based authentication and prefix-based authorization. Use for multi-tenant systems, role-based access control, and isolating workers or services from accessing each other's promises.

Ir para a instalação

Informações da origem

Repositório
resonatehq/resonate-skills
Última atividade na origem
23 de abril de 2026 às 23:47
Idioma detectado do SKILL.md
inglês
Estrelas
6
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
resonate-token-authentication-typescript
description
Secure Resonate applications with JWT-based authentication and prefix-based authorization. Use for multi-tenant systems, role-based access control, and isolating workers or services from accessing each other's promises.
license
Apache-2.0
# Resonate Token Authentication & Authorization (TypeScript) ## Overview Resonate supports JWT-based authentication and prefix-based authorization to secure access to the Resonate server and control which promises clients can access. This enables multi-tenant systems, role-based access control, and isolation between different workers or services. **Core principle:** Generate JWT tokens signed with a private key, configure the Resonate server with the public key, and clients present tokens to authenticate. Optionally use the `prefix` claim for fine-grained authorization. ## Mental Model ``` Without Auth: Client → Resonate Server → All Promises Accessible With Token Auth: Client + Valid JWT → Resonate Server → All Promises Accessible Client + Invalid JWT → Resonate Server → REJECTED (401 Unauthorized) With Prefix Auth (Multi-Tenant): Client + JWT(prefix="tenant-1") → Access only "tenant-1:*" promises Client + JWT(prefix="tenant-2") → Access only "tenant-2:*" promises Client + JWT(prefix="worker-a") → Access only "worker-a:*" promises ``` ## Setup: Generate Keys and Tokens ### 1. Generate RSA Key Pair ```bash # Generate private key (keep this secret!) openssl genrsa -out private_key.pem 2048 # Extract public key (share with Resonate server) openssl rsa -in private_key.pem -pubout -out public_key.pem ``` **Security:** - Store `private_key.pem` securely (never commit to git, use secrets manager) - `public_key.pem` can be deployed with server configuration ### 2. Install JWT CLI Tool ```bash # macOS brew install mike-engel/jwt-cli/jwt-cli # Other platforms # https://github.com/mike-engel/jwt-cli#installation ``` ### 3. Generate JWT Tokens **Basic authentication token (no prefix):** ```bash jwt encode --secret @private_key.pem -A RS256 '{}' ``` **Token with prefix claim (for authorization):** ```bash jwt encode --secret @private_key.pem -A RS256 '{"prefix":"tenant-1"}' ``` **Store token in environment variable:** ```bash export MY_TOKEN=$(jwt encode --secret @private_key.pem -A RS256 '{"prefix":"worker-1"}') ``` ## Server Configuration ### Start Resonate Server with Authentication ```bash # Enable JWT authentication resonate dev --auth-publickey public_key.pem ``` **What this does:** - Enables JWT authentication on all HTTP API endpoints - Validates JWT signatures using the provided public key - Rejects requests with invalid or missing tokens (401 Unauthorized) - Enforces prefix-based authorization if `prefix` claim is present ### Production Deployment ```bash # Using Docker docker run -v $(pwd)/public_key.pem:/keys/public_key.pem \ resonatehq/resonate \ serve --auth-publickey /keys/public_key.pem # Using systemd service (see `resonate-server-deployment`) resonate serve \ --auth-publickey /etc/resonate/public_key.pem \ --storage-type postgres \ --storage-postgres-url postgres://user:pass@db.example.com/resonate ``` For fully worked deploys of the server with auth wired in, see [`resonate-server-deployment`](../resonate-server-deployment/SKILL.md) (Linux/systemd) or [`resonate-server-deployment-cloud-run`](../resonate-server-deployment-cloud-run/SKILL.md) (GCP Cloud Run + Cloud SQL). ## Pattern 1: Basic Token Authentication **Use when:** You want to restrict access to trusted clients only, but all authenticated clients have equal access. ```typescript import { Resonate, Context } from "@resonatehq/sdk"; function* helloAuth(ctx: Context, greeting: string) { const result = yield* ctx.run((ctx: Context) => { return `${greeting} world!`; }); return result; } // Authenticated client const resonate = new Resonate({ url: "http://localhost:8001", token: process.env.MY_TOKEN // JWT token from environment }); // Register and run workflows const workflow = resonate.register("workflow", helloAuth); const result = await workflow.run("workflow.id", "hello"); console.log(result); // "hello world!" resonate.stop(); ``` **Without token:** ```typescript // ❌ This will fail with "ResonateError: The request is unauthorized" const resonateNoAuth = new Resonate({ url: "http://localhost:8001" // No token provided }); const workflow = resonateNoAuth.register("workflow", helloAuth); await workflow.run("workflow.id", "hello"); // THROWS 401 ERROR ``` ## Pattern 2: Prefix-Based Authorization (Multi-Tenant) **Use when:** You need to isolate promises between tenants, workers, or services. ### Server-Side Prefix Enforcement When a client connects with a token containing a `prefix` claim, the Resonate server enforces that the client can only: - Create promises with IDs starting with that prefix - Access promises with IDs starting with that prefix **Generate tenant-specific tokens:** ```bash # Token for Tenant 1 export TENANT1_TOKEN=$(jwt encode -S @private_key.pem -A RS256 '{"prefix":"tenant-1"}') # Token for Tenant 2 export TENANT2_TOKEN=$(jwt encode -S @private_key.pem -A RS256 '{"prefix":"tenant-2"}') # Token for Worker A export WORKER_A_TOKEN=$(jwt encode -S @private_key.pem -A RS256 '{"prefix":"worker-a"}') ``` ### Client with Automatic Prefix ```typescript // Tenant 1 client const tenant1 = new Resonate({ url: "http://localhost:8001", token: process.env.TENANT1_TOKEN, prefix: "tenant-1" // SDK automatically prefixes all promise IDs }); const workflow1 = tenant1.register("workflow", processOrder); // Creates promise with ID "tenant-1:order-123" await workflow1.run("order-123", orderData); // Tenant 2 client (isolated from Tenant 1) const tenant2 = new Resonate({ url: "http://localhost:8001", token: process.env.TENANT2_TOKEN, prefix: "tenant-2" }); const workflow2 = tenant2.register("workflow", processOrder); // Creates promise with ID "tenant-2:order-123" await workflow2.run("order-123", orderData); // Tenant 1 cannot access Tenant 2's promises (and vice versa) ``` ### Client with Manual Prefix ```typescript const resonate = new Resonate({ url: "http://localhost:8001", token: process.env.TENANT1_TOKEN // No prefix set in SDK }); const workflow = resonate.register("workflow", processOrder); // Manually prefix the promise ID await workflow.run("tenant-1:order-123", orderData); ``` **Why manual prefixing?** - More control over promise ID structure - Can use different prefixes for different workflows - Useful for migration or complex ID schemes ## Pattern 3: Multi-Worker Isolation **Use when:** Multiple worker groups should not interfere with each other's promises. ```typescript // Worker Group A const workerA = new Resonate({ url: "http://localhost:8001", token: process.env.WORKER_A_TOKEN, prefix: "worker-a", group: "workers-a" }); workerA.register("processTask", processTask); // Worker Group B (isolated from A) const workerB = new Resonate({ url: "http://localhost:8001", token: process.env.WORKER_B_TOKEN, prefix: "worker-b", group: "workers-b" }); workerB.register("processTask", processTask); // Worker A can only claim tasks prefixed with "worker-a:" // Worker B can only claim tasks prefixed with "worker-b:" ``` ## Pattern 4: Environment Variable Configuration **Use when:** You want to avoid hardcoding credentials in source code. ```typescript // Set environment variables // RESONATE_TOKEN=<jwt-token> // RESONATE_PREFIX=tenant-1 // SDK automatically reads from environment const resonate = new Resonate({ url: "http://localhost:8001" // Token and prefix read from RESONATE_TOKEN and RESONATE_PREFIX }); const workflow = resonate.register("workflow", myWorkflow); await workflow.run("order-123", data); // Creates "tenant-1:order-123" ``` **Environment variables:** - `RESONATE_TOKEN`: JWT token - `RESONATE_PREFIX`: Promise ID prefix ## Pattern 5: Role-Based Access Control (RBAC) **Use when:** Different roles need different levels of access. ```bash # Admin token (access all promises) export ADMIN_TOKEN=$(jwt encode -S @private_key.pem -A RS256 '{}') # Service token (access only service-specific promises) export SERVICE_TOKEN=$(jwt encode -S @private_key.pem -A RS256 '{"prefix":"service-api"}') # User token (access only user-specific promises) export USER_TOKEN=$(jwt encode -S @private_key.pem -A RS256 '{"prefix":"user-${userId}"}') ``` **Client code:** ```typescript // Admin client (no prefix restriction) const admin = new Resonate({ url: "http://localhost:8001", token: process.env.ADMIN_TOKEN }); // Can access any promise ID await admin.promises.get("tenant-1:order-123"); await admin.promises.get("tenant-2:order-456"); // Service client (restricted to service-api:* promises) const service = new Resonate({ url: "http://localhost:8001", token: process.env.SERVICE_TOKEN, prefix: "service-api" }); // Can only access "service-api:*" promises await service.promises.get("request-123"); // OK (becomes "service-api:request-123") ``` ## Token Claims and Validation **Required claims:** - None (empty JWT `{}` is valid for basic authentication) **Optional claims:** - `prefix` (string): Restricts promise access to IDs starting with this prefix - `exp` (number): Token expiration timestamp (Unix epoch) - `iat` (number): Token issued-at timestamp **Token validation:** ```bash # Generate token with expiration (1 hour) jwt encode -S @private_key.pem -A RS256 \ --exp='+1 hour' \ '{"prefix":"tenant-1"}' # Decode and verify token jwt decode -S @public_key.pem $MY_TOKEN ``` ## Security Best Practices ### 1. Protect Private Keys ```bash # ❌ WRONG - Committing keys to git git add private_key.pem # NEVER DO THIS # ✅ CORRECT - Use secrets manager aws secretsmanager create-secret \ --name resonate-jwt-private-key \ --secret-string file://private_key.pem # ✅ CORRECT - Add to .gitignore echo "*.pem" >> .gitignore echo "*.key" >> .gitignore ``` ### 2. Rotate Keys Periodically ```bash # Generate new key pair openssl genrsa -out private_key_v2.pem 2048 openssl rsa -in private_key_v2.pem -pubout -out public_key_v2.pem # Update server configuration resonate serve --auth-publickey public_key_v2.pem # Issue new tokens with new private key jwt encode -S @private_key_v2.pem -A RS256 '{"prefix":"tenant-1"}' ``` ### 3. Use Token Expiration ```bash # Generate short-lived tokens (1 hour) jwt encode -S @private_key.pem -A RS256 --exp='+1 hour' '{}' # Generate long-lived tokens (30 days) jwt encode -S @private_key.pem -A RS256 --exp='+30 days' '{}' ``` ### 4. Least Privilege Prefixes ```typescript // ✅ GOOD - Narrow prefix const client = new Resonate({ token: token, prefix: "user-123" // Can only access user-123's promises }); // ❌ BAD - Wide prefix const client = new Resonate({ token: token, prefix: "user" // Can access all users' promises (user-*, user-123, etc.) }); ``` ## Production Recommendations ### 1. Use External Identity Provider For production systems, integrate with identity providers like: - **Keycloak**: Open-source identity and access management - **Auth0**: Managed authentication service - **Okta**: Enterprise identity platform - **AWS Cognito**: AWS-managed identity service ### 2. Token Refresh Strategy ```typescript // Implement token refresh before expiration let token = await getInitialToken(); let resonate = new Resonate({ url: serverUrl, token }); // Refresh token periodically setInterval(async () => { token = await refreshToken(token); resonate = new Resonate({ url: serverUrl, token }); }, 30 * 60 * 1000); // Refresh every 30 minutes ``` ### 3. Audit Logging ```typescript // Log authentication events
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub