> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fased.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Trusted Proxy Auth

# Trusted Proxy Auth

> **Security-sensitive feature.** This mode delegates authentication entirely to
> your reverse proxy. Misconfiguration can expose your Gateway to unauthorized
> access. Read this page carefully before enabling.

## When to Use

Use `trusted-proxy` auth mode when:

* You run Fased behind an **identity-aware proxy** such as Pomerium, Caddy +
  OAuth, nginx + oauth2-proxy, or Traefik + forward auth
* Your proxy handles all authentication and passes user identity via headers
* You're in a Kubernetes or container environment where the proxy is the only
  path to the Gateway
* You're hitting WebSocket `1008 unauthorized` errors because browsers cannot
  pass tokens in WS payloads

## When NOT to Use

* If your proxy doesn't authenticate users (just a TLS terminator or load balancer)
* If there is any path to the Gateway that bypasses the proxy, such as firewall
  holes or internal network access
* If you're unsure whether your proxy correctly strips/overwrites forwarded headers
* If you only need personal single-user access, consider Tailscale Serve plus
  loopback for a simpler setup

## How It Works

1. Your reverse proxy authenticates users (OAuth, OIDC, SAML, etc.)
2. Proxy adds a header with the authenticated user identity, for example
   `x-forwarded-user: nick@example.com`.
3. Fased checks that the request came from a **trusted proxy IP** configured in
   `gateway.trustedProxies`.
4. Fased extracts the user identity from the configured header
5. If everything checks out, the request is authorized

## Control UI Pairing Behavior

When `gateway.auth.mode = "trusted-proxy"` is active and the request passes
trusted-proxy checks, Control UI WebSocket sessions can connect without device
pairing identity.

Implications:

* Pairing is no longer the primary gate for Control UI access in this mode.
* Your reverse proxy auth policy and `allowUsers` become the effective access
  control.
* Keep gateway ingress locked to trusted proxy IPs only:
  `gateway.trustedProxies` plus firewall.

## Configuration

```json5 theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  gateway: {
    // Current runtime requires lan/custom with direct Gateway access firewalled to the proxy.
    bind: "lan",

    // CRITICAL: Only add your proxy's IP(s) here
    trustedProxies: ["10.0.0.1", "172.17.0.1"],

    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        // Header containing authenticated user identity (required)
        userHeader: "x-forwarded-user",

        // Optional: headers that MUST be present (proxy verification)
        requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"],

        // Optional: restrict to specific users (empty = allow all)
        allowUsers: ["nick@example.com", "admin@company.org"],
      },
    },
  },
}
```

<Warning>
  Current startup rejects `gateway.auth.mode: "trusted-proxy"` with
  `gateway.bind: "loopback"`. Use `bind: "lan"` or `bind: "custom"` and firewall
  the Gateway port so only the proxy can reach it. For same-host proxy setups
  where you want to keep Fased on loopback, use token/password auth behind the
  proxy instead.
</Warning>

### Configuration Reference

* `gateway.trustedProxies` (required): proxy IP addresses to trust. Requests
  from other IPs are rejected.
* `gateway.auth.mode` (required): must be `"trusted-proxy"`.
* `gateway.auth.trustedProxy.userHeader` (required): header containing the
  authenticated user identity.
* `gateway.auth.trustedProxy.requiredHeaders` (optional): extra headers that
  must be present for the request to be trusted.
* `gateway.auth.trustedProxy.allowUsers` (optional): allowlist of user
  identities. Empty means all authenticated users are allowed.

## TLS termination and HSTS

Use one TLS termination point and apply HSTS there.

### Recommended pattern: proxy TLS termination

When your reverse proxy handles HTTPS for `https://control.example.com`, set
`Strict-Transport-Security` at the proxy for that domain.

* Good fit for internet-facing deployments.
* Keeps certificate + HTTP hardening policy in one place.
* Fased should stay reachable only from the proxy. With `trusted-proxy` auth,
  use `bind: "lan"` or `bind: "custom"` plus firewall rules. If you keep Fased
  on loopback HTTP behind a same-host proxy, use token/password auth instead.

Example header value:

```text theme={"theme":{"light":"min-light","dark":"min-dark"}}
Strict-Transport-Security: max-age=31536000; includeSubDomains
```

### Gateway TLS termination

If Fased itself serves HTTPS directly (no TLS-terminating proxy), set:

```json5 theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  gateway: {
    tls: { enabled: true },
    http: {
      securityHeaders: {
        strictTransportSecurity: "max-age=31536000; includeSubDomains",
      },
    },
  },
}
```

`strictTransportSecurity` accepts a string header value, or `false` to disable explicitly.

### Rollout guidance

* Start with a short max age first (for example `max-age=300`) while validating traffic.
* Increase to long-lived values (for example `max-age=31536000`) only after confidence is high.
* Add `includeSubDomains` only if every subdomain is HTTPS-ready.
* Use preload only if you intentionally meet preload requirements for your full domain set.
* Loopback-only local development does not benefit from HSTS.

## Proxy Setup Examples

### Pomerium

Pomerium passes identity in `x-pomerium-claim-email` (or other claim headers) and a JWT in `x-pomerium-jwt-assertion`.

```json5 theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  gateway: {
    bind: "lan",
    trustedProxies: ["10.0.0.1"], // Pomerium's IP
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "x-pomerium-claim-email",
        requiredHeaders: ["x-pomerium-jwt-assertion"],
      },
    },
  },
}
```

Pomerium config snippet:

```yaml theme={"theme":{"light":"min-light","dark":"min-dark"}}
routes:
  - from: https://fased.example.com
    to: http://fased-gateway:18789
    policy:
      - allow:
          or:
            - email:
                is: nick@example.com
    pass_identity_headers: true
```

### Caddy with OAuth

Caddy with the `caddy-security` plugin can authenticate users and pass identity headers.

```json5 theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  gateway: {
    bind: "lan",
    trustedProxies: ["127.0.0.1"], // same-host proxy; firewall direct Gateway access
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "x-forwarded-user",
      },
    },
  },
}
```

Caddyfile snippet:

```
fased.example.com {
    authenticate with oauth2_provider
    authorize with policy1

    reverse_proxy fased:18789 {
        header_up X-Forwarded-User {http.auth.user.email}
    }
}
```

### nginx + oauth2-proxy

oauth2-proxy authenticates users and passes identity in `x-auth-request-email`.

```json5 theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  gateway: {
    bind: "lan",
    trustedProxies: ["10.0.0.1"], // nginx/oauth2-proxy IP
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "x-auth-request-email",
      },
    },
  },
}
```

nginx config snippet:

```nginx theme={"theme":{"light":"min-light","dark":"min-dark"}}
location / {
    auth_request /oauth2/auth;
    auth_request_set $user $upstream_http_x_auth_request_email;

    proxy_pass http://fased:18789;
    proxy_set_header X-Auth-Request-Email $user;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}
```

### Traefik with Forward Auth

```json5 theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  gateway: {
    bind: "lan",
    trustedProxies: ["172.17.0.1"], // Traefik container IP
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "x-forwarded-user",
      },
    },
  },
}
```

## Security Checklist

Before enabling trusted-proxy auth, verify:

* [ ] **Proxy is the only path**: the Gateway port is firewalled from everything
  except your proxy
* [ ] **trustedProxies is minimal**: only your actual proxy IPs, not entire
  subnets
* [ ] **Proxy strips headers**: your proxy overwrites, not appends,
  `x-forwarded-*` headers from clients
* [ ] **TLS termination**: Your proxy handles TLS; users connect via HTTPS
* [ ] **allowUsers is set** (recommended): restrict to known users rather than
  allowing anyone authenticated

## Security Audit

`fased security audit` flags trusted-proxy auth with a **critical** severity
finding. That is intentional: this mode delegates security to your proxy setup.

The audit checks for:

* Missing `trustedProxies` configuration
* Missing `userHeader` configuration
* Empty `allowUsers` (allows any authenticated user)

## Troubleshooting

### "trusted\_proxy\_untrusted\_source"

The request didn't come from an IP in `gateway.trustedProxies`. Check:

* Is the proxy IP correct? (Docker container IPs can change)
* Is there a load balancer in front of your proxy?
* Use `docker inspect` or `kubectl get pods -o wide` to find actual IPs

### "trusted\_proxy\_user\_missing"

The user header was empty or missing. Check:

* Is your proxy configured to pass identity headers?
* Is the header name correct? (case-insensitive, but spelling matters)
* Is the user actually authenticated at the proxy?

### `trusted_proxy_missing_header_*`

A required header wasn't present. Check:

* Your proxy configuration for those specific headers
* Whether headers are being stripped somewhere in the chain

### "trusted\_proxy\_user\_not\_allowed"

The user is authenticated but not in `allowUsers`. Either add them or remove the allowlist.

### WebSocket Still Failing

Make sure your proxy:

* Supports WebSocket upgrades (`Upgrade: websocket`, `Connection: upgrade`)
* Passes the identity headers on WebSocket upgrade requests (not just HTTP)
* Doesn't have a separate auth path for WebSocket connections

## Migration from Token Auth

If you're moving from token auth to trusted-proxy:

1. Configure your proxy to authenticate users and pass headers
2. Test the proxy setup independently (curl with headers)
3. Update Fased config with trusted-proxy auth
4. Restart the Gateway
5. Test WebSocket connections from the Control UI
6. Run `fased security audit` and review findings

## Related

* [Security](/gateway/security) — full security guide
* [Configuration](/gateway/configuration) — config reference
* [Remote Access](/gateway/remote) — other remote access patterns
* [Tailscale](/gateway/tailscale) — simpler alternative for tailnet-only access
