Auth — Authentication & Access Control
Gateway Security Layer
The Auth subsystem guards every connection entering the Gateway. It resolves auth modes from configuration, validates credentials across six methods, enforces rate limiting with sliding-window counters, manages device-token pairing, and applies install-time auth policies.
Security-Critical TypeScript Rate Limiting Multi-Method
What Is This & Why Does It Exist?
Without authentication, any client could connect to the Gateway and execute arbitrary agent commands. The Auth subsystem ensures that every connection is verified before it reaches internal services.
💡 Real-world analogy
Think of Auth as a multi-layered building security system. The resolveGatewayAuth() function is the security policy that determines which type of badge (token, password, Tailscale, device-token) is required today. The AuthRateLimiter is the turnstile that locks after too many failed badge swipes. authorizeGatewayConnect() is the guard who checks your badge against the policy. auth-install-policy.ts is the building code that mandates what security must be installed before opening day.
Table of Contents
Architecture
The Auth layer is the first subsystem touched by every incoming connection. It coordinates between credential resolution, auth mode policy, rate limiting, and multiple authentication strategies.
flowchart TB
subgraph Input
REQ["Incoming Request
ConnectParams"]
end
subgraph AuthLayer["Auth Layer"]
RESOLVE["resolveGatewayAuth()
Mode Resolution"]
POLICY["auth-mode-policy.ts
Ambiguity Detection"]
CRED["credentials.ts
Credential Resolution"]
RL["AuthRateLimiter
Sliding-Window Counter"]
subgraph Methods["Auth Methods"]
NONE["none"]
TOKEN["token"]
PASS["password"]
TAIL["tailscale"]
DEV["device-token"]
PROXY["trusted-proxy"]
end
end
subgraph Output
OK["GatewayAuthResult
{ok: true, method, user}"]
FAIL["GatewayAuthResult
{ok: false, reason}"]
end
REQ --> RL
RL -->|allowed| RESOLVE
RL -->|locked out| FAIL
RESOLVE --> POLICY
POLICY --> CRED
CRED --> Methods
Methods --> OK
Methods -->|failure| FAIL
Data Flow
Sequence of operations when authorizeGatewayConnect() processes an incoming connection request.
sequenceDiagram
participant C as Client
participant RL as AuthRateLimiter
participant Resolve as resolveGatewayAuth
participant Policy as AuthModePolicy
participant Auth as authorizeGatewayConnect
participant Cred as CredentialResolver
C->>RL: check(clientIp, scope)
alt rate limited
RL-->>C: {allowed: false, retryAfterMs}
end
RL-->>Auth: {allowed: true, remaining}
Auth->>Resolve: resolveGatewayAuth(cfg, overrides)
Resolve->>Policy: assertExplicitGatewayAuthMode()
Resolve-->>Auth: ResolvedGatewayAuth {mode, token, password}
Auth->>Cred: resolveGatewayCredentialsFromConfig()
Cred-->>Auth: {token?, password?}
Auth->>Auth: evaluate method (none|token|password|tailscale|device-token|trusted-proxy)
alt success
Auth-->>C: {ok: true, method, user}
else failure
Auth->>RL: recordFailure(clientIp, scope)
Auth-->>C: {ok: false, reason}
end
Key Classes & Types
| Name | File | Description |
|---|---|---|
ResolvedGatewayAuthMode | auth.ts | Union: "none" | "token" | "password" | "trusted-proxy" |
ResolvedGatewayAuth | auth.ts | Resolved config: mode, modeSource, token, password, allowTailscale, trustedProxy |
GatewayAuthResult | auth.ts | Auth outcome: ok, method, user, reason, rateLimited, retryAfterMs |
GatewayAuthSurface | auth.ts | Surface type: "http" | "ws-control-ui" |
AuthorizeGatewayConnectParams | auth.ts | Full auth params (26 fields): auth, connectAuth, req, trustedProxies, rateLimiter, etc. |
AuthRateLimiter | auth-rate-limit.ts | Rate limiter interface: check(), recordFailure(), reset(), prune(), dispose() |
RateLimitConfig | auth-rate-limit.ts | Config: maxAttempts (10), windowMs (60s), lockoutMs (5min), exemptLoopback |
RateLimitCheckResult | auth-rate-limit.ts | Check result: allowed, remaining, retryAfterMs |
RateLimitEntry | auth-rate-limit.ts | Per-IP state: attempts[], lockedUntil? |
ResolvedGatewayCredentials | credentials.ts | Resolved credentials: {token?, password?} |
GatewaySecretRefUnavailableError | credentials.ts | Error thrown when secret reference file is unavailable |
Auth Methods
| Method | Identifier | Mechanism | Use Case |
|---|---|---|---|
none | No auth | All connections accepted | Local development, trusted networks |
token | Bearer token | Static token compared via authorizeTokenAuth() | CLI automation, service-to-service |
password | Password | Password verified from config or env | Web UI access |
tailscale | Tailscale whois | resolveVerifiedTailscaleUser() | Zero-trust mesh network |
device-token | Device pairing | Paired device token exchange | Mobile apps, persistent devices |
trusted-proxy | Proxy header | authorizeTrustedProxy() + browser origin check | Reverse proxy setups (nginx, Caddy) |
Rate Limit Scopes
| Scope Constant | Value | Description |
|---|---|---|
AUTH_RATE_LIMIT_SCOPE_DEFAULT | "default" | General auth attempts |
AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET | "shared-secret" | Shared secret auth attempts |
AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN | "device-token" | Device token pairing attempts |
AUTH_RATE_LIMIT_SCOPE_HOOK_AUTH | "hook-auth" | Hook authentication attempts |
Code Examples
Resolving Auth Mode
import { resolveGatewayAuth, ResolvedGatewayAuth } from './auth';
const auth: ResolvedGatewayAuth = resolveGatewayAuth({
config: openClawConfig,
overrideMode: undefined,
overrideToken: process.env.GATEWAY_TOKEN,
});
// auth.mode => "token" | "password" | "none" | "trusted-proxy"
// auth.modeSource => "config" | "override" | "default"
Creating a Rate Limiter
import { createAuthRateLimiter, RateLimitConfig } from './auth-rate-limit';
const config: RateLimitConfig = {
maxAttempts: 10,
windowMs: 60_000,
lockoutMs: 300_000,
exemptLoopback: true,
pruneIntervalMs: 60_000,
};
const limiter = createAuthRateLimiter(config);
const result = limiter.check('192.168.1.100', 'default');
// result => { allowed: true, remaining: 10, retryAfterMs: 0 }
Authorizing a Connection
import { authorizeGatewayConnect, GatewayAuthResult } from './auth';
const result: GatewayAuthResult = await authorizeGatewayConnect({
auth: resolvedAuth,
connectAuth: { token: clientToken },
req: incomingRequest,
trustedProxies: ['10.0.0.0/8'],
authSurface: 'ws-control-ui',
rateLimiter: limiter,
clientIp: '192.168.1.100',
rateLimitScope: 'default',
allowRealIpFallback: false,
browserOriginPolicy: 'strict',
});
if (!result.ok) {
console.error(`Auth failed: ${result.reason}`);
if (result.rateLimited) {
console.error(`Retry after ${result.retryAfterMs}ms`);
}
}
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
auth.mode | ResolvedGatewayAuthMode | "none" | Active auth mode |
auth.token | string | — | Bearer token value |
auth.password | string | — | Password value |
auth.allowTailscale | boolean | false | Accept Tailscale identity |
auth.trustedProxy | GatewayTrustedProxyConfig | — | Trusted proxy CIDR ranges |
rateLimit.maxAttempts | number | 10 | Max failed attempts per window |
rateLimit.windowMs | number | 60000 | Sliding window duration |
rateLimit.lockoutMs | number | 300000 | Lockout duration (5 min) |
rateLimit.exemptLoopback | boolean | true | Skip rate limiting for 127.0.0.1 |
rateLimit.pruneIntervalMs | number | 60000 | Stale entry cleanup interval |
Deep-Dive Focus Pages
🔗 Device Pairing Flow Focus
The device-token pairing protocol that enables persistent mobile & IoT device authentication without shared secrets.
⏱ Rate Limiting Focus
Sliding-window rate limiter with per-scope counters, configurable lockouts, and loopback exemptions.
📜 Install Policies Focus
Install-time and auth-mode policies that enforce security requirements during initial deployment.
Directory Map
Source files for Auth
src/gateway/
├── auth.ts ← Core: resolveGatewayAuth, authorizeGatewayConnect, 6 auth methods
├── auth-rate-limit.ts ← AuthRateLimiter, createAuthRateLimiter, RateLimitConfig
├── auth-mode-policy.ts ← hasAmbiguousGatewayAuthModeConfig, assertExplicitMode
├── auth-install-policy.ts ← shouldRequireGatewayTokenForInstall
├── auth-config-utils.ts ← withGatewayAuthPassword, resolveGatewayPasswordSecretRef
├── credentials.ts ← ResolvedGatewayCredentials, GatewaySecretRefUnavailableError
├── connection-auth.ts ← resolveGatewayConnectionAuth (async credential resolution)
└── connection-details.ts ← GatewayConnectionDetails