Skip to main content

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​

/etc/jiandu/jiandu.json
{
"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:

/etc/nginx/conf.d/jiandu.conf
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:

/etc/nginx/snippets/jiandu-proxy-secret.conf
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:

  1. an authenticated allowed user completes a new Jiandu session;
  2. an unauthenticated request is stopped by the gateway;
  3. a user outside the exact required group is denied;
  4. sending forged X-Auth-* or X-Jiandu-Proxy-Secret headers does not change the asserted user;
  5. Jiandu's port is unreachable without the gateway; and
  6. restarting the gateway without an assertion never falls back to a shared/default subject.
SymptomBoundary to inspect
trusted_proxy_invalid_assertionDirect peer CIDR, secret equality, missing/duplicate subject header, or control/size limits
trusted_proxy_admission_deniedExact comma-separated group value or matching invitation
Every person becomes one Jiandu accountGateway is sending a mutable/shared subject rather than a per-person immutable ID
Browser can choose its identityPublic headers were appended/preserved instead of overwritten after authentication
Works by public URL but not on the session endpointGateway 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.