Trusted authentication proxy
Trusted-proxy authentication is an expert compatibility path for an access gateway that has already authenticated the browser but cannot expose OIDC. Ordinary Caddy, Nginx, or Traefik TLS proxying does not require this provider. Prefer OIDC when the gateway supports it because OIDC's signed protocol keeps identity proof separate from application routing.
Jiandu accepts a proxy assertion only when all three independent proofs agree:
direct TCP peer in trusted CIDR
+
shared X-Jiandu-Proxy-Secret
+
one bounded immutable subject header
=
eligible identity assertion
The normal exact Host, Origin, HTTPS, admission, local membership, and authorization checks still apply. A client-supplied header by itself is never identity.
1. Design the hop before configuring it
- Put the authenticating gateway on a dedicated loopback or pinned private address.
- Make Jiandu's backend port reachable only from that gateway.
- Strip every configured assertion header at the public edge, then set authoritative values after authentication.
- Use an immutable gateway subject such as an internal UUID—not email, username, or display name.
- Generate a separate high-entropy secret for this Jiandu deployment; do not reuse a cookie or OIDC client secret.
The gateway must protect all Jiandu paths, including
POST /api/v1/auth/trusted-proxy/{provider_id}/session. Bypassing authentication for assets is safe
only if the bypass cannot reach API paths and cannot inject assertion headers into the upstream.
2. Create and deliver the shared secret
Generate at least 32 random bytes and deliver the same value independently to Jiandu and the
gateway. Jiandu's copy must be a single regular mode-0600 file:
umask 077
install -d -m 0700 /etc/jiandu/secrets
openssl rand -base64 48 > /etc/jiandu/secrets/proxy-auth-secret
chmod 0600 /etc/jiandu/secrets/proxy-auth-secret
Do not print the file, put the value in JSON, or send it to the browser. If two Unix service users
cannot read the same mode-0600 file, provision two owner-only copies with identical contents
through your secret manager. Rotate both copies together and restart/reload both services inside one
maintenance window.
3. Configure the ingress and provider together
{
"schema_version": 1,
"server": {
"bind": "127.0.0.1:8077",
"ingress": {
"mode": "https_reverse_proxy",
"public_origin": "https://documents.example.net",
"trusted_proxy_cidrs": ["127.0.0.1/32", "::1/128"]
}
},
"auth": {
"providers": [
{
"kind": "trusted_proxy",
"id": "access-gateway",
"display_name": "Household SSO",
"subject_header": "x-auth-subject",
"display_name_header": "x-auth-name",
"groups_header": "x-auth-groups",
"secret_path": "/etc/jiandu/secrets/proxy-auth-secret",
"admission_policy": "required_group",
"admission_group": "jiandu-members"
}
]
}
}
Header names must be unique, lowercase, and safe HTTP header names. The subject is required and may be at most 512 bytes. Display name is presentation only. Groups are a comma-separated list; group names containing commas cannot be represented safely and should be replaced with a gateway-owned stable label.
For invitation_only, remove groups_header and admission_group entirely. Trusted-proxy
providers do not support open. Group admission is evaluated on every sign-in but never maps the
group to a Jiandu role.
4. Nginx auth_request pattern
This pattern asks an authentication gateway to verify the request, captures its protected response headers, and overwrites any browser values before proxying to Jiandu:
server {
listen 443 ssl;
server_name documents.example.net;
ssl_certificate /etc/nginx/tls/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/privkey.pem;
location = /_jiandu_auth {
internal;
proxy_pass http://auth-gateway:4180/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
}
location / {
auth_request /_jiandu_auth;
auth_request_set $auth_subject $upstream_http_x_auth_subject;
auth_request_set $auth_name $upstream_http_x_auth_name;
auth_request_set $auth_groups $upstream_http_x_auth_groups;
proxy_pass http://127.0.0.1:8077;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Auth-Subject $auth_subject;
proxy_set_header X-Auth-Name $auth_name;
proxy_set_header X-Auth-Groups $auth_groups;
include /etc/nginx/snippets/jiandu-proxy-secret.conf;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
}
The protected include contains exactly one Nginx directive with the same secret value:
proxy_set_header X-Jiandu-Proxy-Secret "<SAME-HIGH-ENTROPY-SECRET>";
Make that include readable only by the Nginx master/service and exclude it from source control and
backups that are not secret-bearing. Nginx's proxy_set_header directives replace browser values;
the internal auth response—not the public request—sets $auth_subject, $auth_name, and
$auth_groups. Adapt the /verify URL and response header mapping to the gateway's documented
contract. A redirect-style auth endpoint is not interchangeable with a 2xx/401 auth_request
verification endpoint.
For Caddy or Traefik, preserve the same ordering: remove public assertion headers, authenticate, copy only the gateway result, inject the secret on the private upstream hop, and keep the direct peer pinned. Their ordinary reverse-proxy examples intentionally strip these headers and must be extended—not replaced—when this provider is enabled.
5. Validate rejection as well as success
jiandu --config /etc/jiandu/jiandu.json --check-config
curl --fail --silent https://documents.example.net/api/v1/auth/methods \
| jq '.trustedProxyProviders'
Then prove all of these behaviors from an ordinary client:
- an authenticated allowed user completes a new Jiandu session;
- an unauthenticated request is stopped by the gateway;
- a user outside the exact required group is denied;
- sending forged
X-Auth-*orX-Jiandu-Proxy-Secretheaders does not change the asserted user; - Jiandu's port is unreachable without the gateway; and
- restarting the gateway without an assertion never falls back to a shared/default subject.
| Symptom | Boundary to inspect |
|---|---|
trusted_proxy_invalid_assertion | Direct peer CIDR, secret equality, missing/duplicate subject header, or control/size limits |
trusted_proxy_admission_denied | Exact comma-separated group value or matching invitation |
| Every person becomes one Jiandu account | Gateway is sending a mutable/shared subject rather than a per-person immutable ID |
| Browser can choose its identity | Public headers were appended/preserved instead of overwritten after authentication |
| Works by public URL but not on the session endpoint | Gateway bypass/routing rule does not cover the provider-specific POST path |
Treat a leaked shared secret as an incident even though the peer check is independent: replace both copies, reload the gateway, restart Jiandu, and verify that the backend network still excludes all other peers.