What is this? A Go reverse proxy that sits in front of OpenClaw, authenticating every request through AuthSec before forwarding it to the OpenClaw Gateway. This gives you per-user identity, browser login, session management, multi-tenancy, and audit logging.
- Prerequisites
- How It Works
- Step 1: Clone the Repository
- Step 2: Get AuthSec Credentials
- Step 3: Choose Your AuthSec Mode
- Step 4: Run with Docker Compose
- Step 5: Run Locally (Development)
- Step 6: Run with the Startup Script
- Step 7: Verify Everything Works
- Step 8: Connect Clients
- Environment Variable Reference
- AuthSec Mode Comparison
- Multi-Tenant Setup
- Production Deployment
- Troubleshooting
| Requirement | Version | Notes |
|---|---|---|
| Docker + Docker Compose | v2+ | For containerized deployment |
| Go | 1.22+ | Only for local development |
| openssl | Any | For generating secrets |
| An AuthSec account | — | Sign up at authsec.ai |
| An OpenClaw instance | — | Either standalone or bundled via Docker Compose |
┌──────────┐ ┌──────────────────────┐ ┌─────────────────────┐
│ Client │ HTTPS / WSS │ bot-auth Proxy │ internal network │ OpenClaw Gateway │
│ │ ────────────────→ │ (Go, port 8080) │ ────────────────→ │ (port 18789) │
│ Browser │ │ │ │ │
│ CLI │ ←────────────────│ Authenticates users │ Injects gateway │ Never exposed │
│ API │ Responses │ via AuthSec JWT │ token + user ID │ to the public │
└──────────┘ └──────────────────────┘ └─────────────────────┘
The proxy:
- Intercepts every inbound request
- Authenticates the user (session cookie or Bearer JWT)
- Authorizes via AuthSec permissions API (
/uflow/user/permissions/check) - Proxies the request to OpenClaw with the internal gateway token
- Never exposes the OpenClaw gateway token to clients
git clone https://github.com/authsec-ai/authsec-openclaw.git
cd authsec-openclawYou need different credentials depending on which AuthSec mode you choose. There are three modes:
-
Sign in to your AuthSec admin panel:
https://<your-tenant>.app.authsec.ai/admin/loginExample:
https://package.app.authsec.ai/admin/login -
Navigate to Settings > API Keys (or Security > JWT)
-
Copy these values:
Value Where to Find It Environment Variable HS256 signing secret API Keys / JWT Settings AUTHSEC_JWT_SECRETLogin page URL Your tenant login URL AUTHSEC_LOGIN_URLAPI base URL Your tenant's API base AUTHSEC_BASE_URLAdmin email The email you used to sign up AUTHSEC_ADMIN_EMAIL
- Create an OIDC client in your AuthSec dashboard
- Note the issuer URL, client ID, and client secret
- These map to
AUTHSEC_ISSUER_URL,AUTHSEC_CLIENT_ID,AUTHSEC_CLIENT_SECRET
No credentials needed. Uses a fake auth provider that accepts Bearer stub:email@example.com.
| Mode | Use Case | Auth Method | Requirements |
|---|---|---|---|
authsec |
Production with AuthSec native API | HS256 JWT verification + permissions API | JWT secret, base URL, login URL |
sdk |
Production with OIDC provider | Standard OIDC (discovery + JWKS) | Issuer URL, client ID, client secret |
stub |
Local development / testing | Fake tokens (stub:email) |
Nothing |
This is the recommended way to run everything. Docker Compose starts both OpenClaw and the proxy.
cd deploy/docker-compose
cp .env .env.localEdit .env.local with your values:
# ─── Required for all modes ───────────────────────────────────────
PROXY_PUBLIC_BASE_URL=http://localhost:8080
PROXY_HTTP_PORT=8080
# ─── OpenClaw connection (auto-configured in Docker Compose) ──────
OPENCLAW_UPSTREAM_URL=http://openclaw:18789
OPENCLAW_GATEWAY_TOKEN=<generate with: openssl rand -hex 32>
OPENCLAW_TRUSTED_PROXIES=172.20.0.0/16
# ─── Session security ─────────────────────────────────────────────
SESSION_HMAC_SECRET=<generate with: openssl rand -hex 32>
# ─── AuthSec mode ─────────────────────────────────────────────────
AUTHSEC_MODE=authsec
# ─── For AUTHSEC_MODE=authsec (native) ───────────────────────────
AUTHSEC_JWT_SECRET=<from AuthSec admin panel>
AUTHSEC_BASE_URL=https://package.app.authsec.ai
AUTHSEC_LOGIN_URL=https://package.app.authsec.ai/login
AUTHSEC_ADMIN_EMAIL=admin@example.com
# ─── For AUTHSEC_MODE=sdk (OIDC) ─────────────────────────────────
# AUTHSEC_ISSUER_URL=https://your-tenant.authsec.ai
# AUTHSEC_CLIENT_ID=your-client-id
# AUTHSEC_CLIENT_SECRET=your-client-secret
# AUTHSEC_AUDIENCE=
# ─── Logging ──────────────────────────────────────────────────────
LOG_LEVEL=info# Generate both secrets in one go
echo "OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)"
echo "SESSION_HMAC_SECRET=$(openssl rand -hex 32)"Copy the output into your .env.local file.
# HTTP only (development)
docker compose --env-file .env.local up --build
# With TLS via Caddy (production)
docker compose --env-file .env.local --profile tls up --build
# Detached mode
docker compose --env-file .env.local up --build -d| Service | Port | Description |
|---|---|---|
openclaw |
18789 (internal only) | OpenClaw Gateway — NOT exposed to host |
proxy |
8080 (host) | AuthSec proxy — the public entry point |
caddy |
80/443 (host) | TLS termination (only with --profile tls) |
If you prefer to run the Go proxy directly on your machine:
# Option 1: Use Docker
docker run -d \
-e OPENCLAW_GATEWAY_TOKEN=dev-token-change-me \
-p 18789:18789 \
ghcr.io/openclaw/openclaw:latest
# Option 2: Use the OpenClaw CLI
openclaw gateway --bind loopback --port 18789export AUTHSEC_MODE=stub # Use stub for quick dev
export OPENCLAW_UPSTREAM_URL=http://localhost:18789
export OPENCLAW_GATEWAY_TOKEN=dev-token-change-me
export PROXY_PUBLIC_BASE_URL=http://localhost:8080
export SESSION_HMAC_SECRET=$(openssl rand -hex 32)
export STUB_ALLOWLIST=you@example.com# Build
go build ./cmd/authsec-openclaw-proxy
# Run
./authsec-openclaw-proxy
# Or run directly
go run ./cmd/authsec-openclaw-proxy# Health check
curl http://localhost:8080/healthz
# Authenticated request (stub mode)
curl -H "Authorization: Bearer stub:you@example.com" http://localhost:8080/
# Browser login
open http://localhost:8080The scripts/start-authsec.sh script automates the setup for authsec native mode:
# Set the two required values
export AUTHSEC_JWT_SECRET=<from-admin-panel>
export AUTHSEC_LOGIN_URL=https://package.app.authsec.ai/login
# Run with Docker Compose (default)
./scripts/start-authsec.sh docker
# Or run the proxy locally (you start OpenClaw separately)
./scripts/start-authsec.sh local
# Or just print the env vars for manual use
./scripts/start-authsec.sh envThe script automatically:
- Generates
OPENCLAW_GATEWAY_TOKENif not set - Generates
SESSION_HMAC_SECRETif not set - Sets
AUTHSEC_BASE_URLtohttps://package.app.authsec.ai - Sets
AUTHSEC_ADMIN_EMAILto the default admin email - Validates that required secrets are present
- Writes a
.env.authsecfile for Docker Compose - Starts the full stack
# Proxy health
curl http://localhost:8080/healthz
# Expected: 200 OK
# Ready check
curl http://localhost:8080/readyz
# Expected: 200 OK- Open
http://localhost:8080in a browser - You should be redirected to the AuthSec login page
- After authenticating, you're redirected back with a session cookie
- Subsequent requests use the session cookie
# With a real AuthSec JWT
curl -H "Authorization: Bearer <your-jwt>" http://localhost:8080/
# With stub mode
curl -H "Authorization: Bearer stub:admin@example.com" http://localhost:8080/# Docker Compose
docker compose logs -f proxy
# Local
# Logs print to stdout in JSON format# Using the authsec-openclaw-cli
./authsec-openclaw-cli login --proxy-url http://localhost:8080
./authsec-openclaw-cli run-tui
# Or configure OpenClaw CLI directly to connect through the proxy
openclaw connect --remote-url ws://localhost:8080 --remote-token <your-jwt># Any HTTP client with Bearer token
curl -H "Authorization: Bearer <jwt>" http://localhost:8080/api/v1/chat
# WebSocket connection
wscat -c ws://localhost:8080/ws -H "Authorization: Bearer <jwt>"Navigate to http://localhost:8080. The proxy handles the login redirect flow automatically.
| Variable | Required | Default | Description |
|---|---|---|---|
PROXY_PUBLIC_BASE_URL |
Yes | — | Public URL of the proxy (for OAuth redirects) |
LISTEN_ADDR |
No | :8080 |
Address the proxy listens on |
OPENCLAW_UPSTREAM_URL |
No | http://openclaw:18789 |
OpenClaw Gateway URL |
OPENCLAW_GATEWAY_TOKEN |
Yes | — | Shared token for OpenClaw Gateway |
SESSION_HMAC_SECRET |
Yes | — | HMAC key for session cookies (min 32 chars) |
SESSION_COOKIE_NAME |
No | oc_session |
Session cookie name |
SESSION_TTL |
No | 8h |
Session lifetime |
SESSION_SECURE_COOKIE |
No | true |
Set false for HTTP (dev only) |
LOG_LEVEL |
No | info |
Log level |
| Variable | Mode | Required | Default | Description |
|---|---|---|---|---|
AUTHSEC_MODE |
All | No | sdk |
Auth mode: authsec, sdk, or stub |
AUTHSEC_JWT_SECRET |
authsec |
Yes | — | HS256 signing key for JWT verification |
AUTHSEC_BASE_URL |
authsec |
Yes | — | AuthSec API base URL |
AUTHSEC_LOGIN_URL |
authsec |
Yes | — | AuthSec web login page URL |
AUTHSEC_ISSUER_URL |
sdk |
Yes | — | OIDC issuer URL |
AUTHSEC_CLIENT_ID |
sdk |
Yes | — | OIDC client ID |
AUTHSEC_CLIENT_SECRET |
sdk |
Yes | — | OIDC client secret |
AUTHSEC_AUDIENCE |
Both | No | — | JWT audience claim |
AUTHSEC_ADMIN_EMAIL |
Both | No | — | Admin email (bypasses authorization) |
AUTHSEC_ADMIN_SUB |
Both | No | — | Admin subject (bypasses authorization) |
AUTHSEC_REQUIRED_CLAIM |
Both | No | — | Required claim (e.g. groups:openclaw) |
AUTHSEC_SCOPES |
sdk |
No | openid,profile,email |
OIDC scopes |
| Variable | Required | Default | Description |
|---|---|---|---|
RATE_LIMIT_PER_MINUTE |
No | 10 |
Auth endpoint rate limit per IP |
MAX_REQUEST_BODY_BYTES |
No | 1048576 |
Max request body size (1MB) |
TRUSTED_PROXY_IPS |
No | — | Comma-separated trusted proxy IPs |
| Variable | Required | Default | Description |
|---|---|---|---|
TENANT_ROUTING_MODE |
No | host |
host or path |
TENANT_DEFAULT |
No | — | Default tenant ID |
TENANT_CONFIG_JSON |
No | — | JSON map of tenant configs |
Client → Proxy → [HS256 JWT verify locally] → [AuthSec permissions API] → OpenClaw
- Verifies JWT signatures locally (fast, no round-trip for validation)
- Calls AuthSec API only for authorization decisions
- Supports browser login with redirect flow
- Requires:
AUTHSEC_JWT_SECRET,AUTHSEC_BASE_URL,AUTHSEC_LOGIN_URL
Client → Proxy → [OIDC discovery + JWKS verify] → OpenClaw
- Standard OIDC flow (works with any OIDC provider)
- Full OIDC discovery (
.well-known/openid-configuration) - Supports device-code flow for CLI
- Requires:
AUTHSEC_ISSUER_URL,AUTHSEC_CLIENT_ID,AUTHSEC_CLIENT_SECRET
Client → Proxy → [Accept any "stub:email" token] → OpenClaw
- No real authentication — accepts
Bearer stub:email@example.com - Optional allowlist via
STUB_ALLOWLIST - Never use in production
export TENANT_ROUTING_MODE=host
export TENANT_CONFIG_JSON='{
"acme": {
"upstream_url": "http://openclaw-acme:18789",
"gateway_token": "token-for-acme"
},
"globex": {
"upstream_url": "http://openclaw-globex:18789",
"gateway_token": "token-for-globex"
}
}'Requests to acme.proxy.example.com route to the acme OpenClaw instance.
export TENANT_ROUTING_MODE=path
# Same TENANT_CONFIG_JSON as aboveRequests to proxy.example.com/acme/... route to the acme OpenClaw instance.
- Set
AUTHSEC_MODE=authsecorAUTHSEC_MODE=sdk - Generate strong secrets:
openssl rand -hex 32for each - Set
SESSION_SECURE_COOKIE=true - Enable TLS (Caddy profile or external load balancer)
- Set
PROXY_PUBLIC_BASE_URLto your real domain (e.g.,https://openclaw.company.com) - Set
AUTHSEC_ADMIN_EMAILto the admin's real email - Verify OpenClaw Gateway is NOT exposed to the public network
- Review rate limiting (
RATE_LIMIT_PER_MINUTE) - Consider multi-tenant setup if serving multiple organizations
cd deploy/docker-compose
# Edit Caddyfile with your domain
# Edit .env.local with production values
docker compose --env-file .env.local --profile tls up -dSee deploy/helm/authsec-openclaw/README.md for Helm chart deployment.
You're running in authsec mode but haven't set the JWT signing secret. Get it from your AuthSec admin panel (Settings > API Keys).
Generate one: openssl rand -hex 32. This is the shared secret between the proxy and OpenClaw — it is NOT the user's JWT.
Generate one: openssl rand -hex 32. This signs session cookies.
- Check that
OPENCLAW_UPSTREAM_URLmatches OpenClaw's actual address - In Docker Compose, use the service name:
http://openclaw:18789 - Locally, use:
http://localhost:18789
The AUTHSEC_JWT_SECRET doesn't match the key that signed the JWT. Verify you copied the correct signing key from the AuthSec admin panel.
The JWT has expired. The user needs to re-authenticate to get a fresh token.
- Check that
PROXY_PUBLIC_BASE_URLmatches the actual URL you're accessing - Check that
AUTHSEC_LOGIN_URLis correct - Check that
SESSION_SECURE_COOKIE=falseif you're using HTTP (dev only)
The default rate limit is 10 requests per minute per IP on auth endpoints. Adjust with RATE_LIMIT_PER_MINUTE.