Forward auth lets your application decide whether each public HTTP request may reach a VM. Freestyle terminates HTTPS, asks your authorization endpoint, and forwards only allowed requests.
Protect A Public Domain
Create one reusable forwardAuth configuration, then attach it to a public HTTP
TLS rule:
const auth = await freestyle.tls.forwardAuth.create({
url: "https://auth.acme.com/freestyle/check",
// Authenticates Freestyle to your authorization service. Values are
// write-only and read back as "***".
headers: { authorization: "Bearer service-to-service-secret" },
timeoutMs: 1500,
// Copy identity established by the authorizer into the VM request.
authResponseHeaders: ["x-acme-user-id", "x-acme-team-id"],
// These browser cookies reach the authorizer, but never the untrusted VM.
protectedCookies: ["__Host-acme-session", "__Host-acme-login"],
});
await freestyle.tls.rules.create({
action: "allow",
domain: "preview.acme.com",
source: { public: true },
destination: { vmId: "vm-123456", port: 3000 },
forwardAuth: { id: auth.id },
});
forwardAuth is supported only on terminated HTTP public ingress rules:
{ public: true } to one VM port. One configuration may protect many rules.
Authorization Request And Response
The authorization URL must be a publicly reachable HTTPS endpoint. For each
browser request, Freestyle sends it a bodyless GET with the caller’s
Authorization and Cookie headers, the configured static headers, and
trusted request metadata in X-Forwarded-Method, X-Forwarded-Proto,
X-Forwarded-Host, X-Forwarded-Uri, and X-Forwarded-For, and the id of the
matched rule in X-Freestyle-TLS-Rule-Id.
The response controls the request:
- A
2xxallows it. Headers named byauthResponseHeadersreplace the caller’s values on the request sent to the VM. - A
3xxor4xxis returned to the browser, including its response body and end-to-end headers. Freestyle does not follow redirects, so the authorizer can redirect a signed-out user to a login flow. - A timeout, network or DNS failure, invalid response, or
5xxfails closed; Freestyle returns503and does not send the request to the VM.
Every cookie named by protectedCookies remains visible to the authorizer but
is removed before the request reaches the VM. The VM also cannot set, replace,
or clear one of those cookies. On an allowed request, matching Set-Cookie
headers from the authorizer are preserved in the response to the browser.
Read, Update, And Delete Configurations
Get, list, replace, and delete configurations through the same namespace:
const saved = await freestyle.tls.forwardAuth.get(auth.id);
const { configs, totalCount } = await freestyle.tls.forwardAuth.list();
await freestyle.tls.forwardAuth.update(auth.id, {
url: saved.url,
headers: { authorization: "Bearer rotated-service-secret" },
timeoutMs: saved.timeoutMs,
authResponseHeaders: saved.authResponseHeaders,
protectedCookies: saved.protectedCookies,
});
// Remove or update every referring TLS rule before deleting the configuration.
await freestyle.tls.forwardAuth.delete(auth.id);
update replaces the complete configuration. Because static header values are
write-only, supply them again on every update.
Behind A Reverse Proxy
X-Freestyle-TLS-Rule-Id carries the id of the TLS rule that matched the
request. It is the one piece of request identity to trust when your authorizer
decides which domain and destination a request is for, because it maps
one-to-one to the rule you created and Freestyle stamps it on every check.
Do not key that decision on X-Forwarded-Host. When your authorization endpoint
sits behind a reverse proxy — Vercel, Cloudflare, an API gateway, nginx — that
proxy terminates the request and commonly overwrites the X-Forwarded-*
headers, X-Forwarded-Host included, with its own values before your handler
runs. The host you read is then the proxy’s, and every protected domain
collapses to the same value, so a lookup keyed on it matches nothing.
X-Freestyle-TLS-Rule-Id is not a standard forwarding header, so proxies pass it
through untouched.
So resolve the protected domain and its policy by the rule id, and treat
X-Forwarded-Host as an untrusted hint at most. If you need the caller’s
original host, record it against the rule id when you create the rule and read
it back from the rule id on each check, rather than trusting a forwarding header
a proxy may have rewritten.