Sign out cleared ipx's cookies and showed its password page, while Cloudflare Access still vouched for the person: nothing was signed out, and the page looked like the wrong login. /api/me now says, for someone the proxy signed in, where to go instead ([web] sign_out_url, which is /cdn-cgi/access/logout behind Access), and /login sends anyone the proxy vouches for on to their feeds. The header check both use is one function. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAC7sLVqfKmY6rsTLXzNgk
9.8 KiB
Signing in through Cloudflare Access and Authentik
ipx can take the signed-in identity from whatever sits in front of it, instead of asking for a password itself. The proxy authenticates the person and passes the result to ipx in a header; ipx reads it, finds (or creates) the matching account, and gets on with it.
Read How this is secured before exposing anything. The short version: a header is worth exactly as much as the hop that set it, so ipx only believes one from an address you list.
How ipodderx.sdf1.net does it
Checked end to end on 2026-09-12. An earlier version of this page had never been tried against a real setup and pointed at the wrong address.
browser ─► Cloudflare Access, app "ipodderx" ─── sign in ───► Authentik (OpenID Connect)
─► tunnel "rays-unraid" (the cloudflared container on Tower)
─► http://192.168.1.130:8099 ─► ipx
Authentik is not in the request path. It is the identity provider Cloudflare Access asks. Access
then adds Cf-Access-Authenticated-User-Email, the email address Authentik gave it, to every
request it forwards through the tunnel, and ipx signs that person in.
| Piece | Where | Setting |
|---|---|---|
| Identity provider | Zero Trust → Settings → Authentication | Authentik, OpenID Connect; scopes openid email profile |
| Access application | Zero Trust → Access → Applications → ipodderx |
Domain ipodderx.sdf1.net; identity providers: Authentik only, with instant auth; session 730h; policy Require Login allows a list of email addresses |
| Tunnel route | Zero Trust → Networks → Tunnels → rays-unraid → Public hostnames |
ipodderx.sdf1.net → HTTP 192.168.1.130:8099 |
| DNS | sdf1.net |
ipodderx CNAME to the tunnel, proxied |
| ipx | /mnt/fast/appdata/ipodderx/config.toml, [web] |
below |
[web]
enabled = true
bind = "0.0.0.0:8099"
trusted_header = "Cf-Access-Authenticated-User-Email"
trusted_proxies = ["127.0.0.1", "::1", "192.168.16.1"]
auto_create_users = true
sign_out_url = "/cdn-cgi/access/logout"
session_days = 30
Restart ipx after editing it: docker compose -f /mnt/fast/arcane/projects/content/compose.yaml restart ipodderx.
What was missing
Cloudflare and Authentik were already right. Three things on the ipx side were not:
trusted_headerwas empty, which switches the whole proxy path off. ipx ignored the header and asked for a password.trusted_proxieslisted only127.0.0.1. The tunnel's requests do not come from there; see the next section.- The account had the wrong name. It was made by hand as
rays, but the header carriesrays@sdf1.net. Withauto_create_userson, the first visit would have made a second, empty account.ipx user rename rays rays@sdf1.netfixed that without losing anything.
The address to trust, and why it is 192.168.16.1
cloudflared runs in its own container and reaches ipx through the host's published port. Docker
(iptables firewall backend) masquerades traffic between its bridge networks, so the tunnel's
requests arrive from the gateway of ipx's own network, content_default:
docker network inspect content_default -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}'
That was measured, not assumed. ipx does not log where a request came from, so the addresses were
read from the kernel's connection table inside the container while the site was open. (/proc/net/tcp
lists them in hex.)
If the content project's network is ever recreated, its gateway can change. Check it again, and
update trusted_proxies to match.
Names
The username is the email address, lower-cased: rays@sdf1.net. To sign in at /login with a
password from the LAN, use that name too.
To let someone else in, add their address to the Access policy; they need an Authentik account with
that email. With auto_create_users = true they get an ipx account on their first visit, as an
ordinary user with no feeds. An account made before the proxy can be given the name the proxy will
send:
docker exec iPodderX ipx user rename <old name> <email address>
Signing out
Sign out sends someone the proxy signed in to sign_out_url, here Cloudflare's
/cdn-cgi/access/logout. That ends your Access session for every Access application,
code.sdf1.net included: Cloudflare has no way to end just one, and its sign-out page does not send
you anywhere afterwards. The next visit goes back through Authentik, which lets you straight in if
you are still signed in there. Signing out of Authentik itself is Authentik's own sign-out.
ipx never shows its password page to someone the proxy vouches for: /login sends them on to their
feeds.
The tile in Authentik's library
Authentik's library lists Authentik's own applications, and ipodderx signs in through the one
called Cloudflare Access, so ipodderx needs a bookmark of its own to show up there. It is
Applications → Applications → ipodderx: no provider, launch URL https://ipodderx.sdf1.net, and
the iPodderX icon. Like Outline's, it has no policy bindings, so everyone in Authentik sees the
tile. Who actually gets in is still up to the Access policy.
Check it
# From Tower itself: not a trusted address, so the header is ignored.
curl -s -H 'Accept: application/json' -H 'Cf-Access-Authenticated-User-Email: rays@sdf1.net' \
http://192.168.1.130:8099/api/me # -> sign in
# From a container on a Docker bridge, as cloudflared is: believed.
docker run --rm --network bridge mirror.gcr.io/library/busybox wget -qO- \
--header 'Accept: application/json' --header 'Cf-Access-Authenticated-User-Email: rays@sdf1.net' \
http://192.168.1.130:8099/api/me # -> {"admin":true,"name":"rays@sdf1.net"}
Then open https://ipodderx.sdf1.net in a private window. Authentik should ask who you are, and
ipx should show rays@sdf1.net in the sidebar footer without asking for a password.
The ipx settings
| Key | What it does |
|---|---|
trusted_header |
The header the proxy sets. Empty, the default, turns the proxy path off. |
trusted_proxies |
The addresses allowed to set it. Nothing else is believed. |
auto_create_users |
Make an account the first time the proxy vouches for a name ipx has not seen. |
sign_out_url |
Where Sign out sends someone the proxy signed in: the proxy's own sign-out. Empty sends them to the sign-in page, where the proxy signs them straight back in. |
session_days |
How long a password sign-in lasts without use. |
The first account ever created is an admin. Every later one is an ordinary user, who cannot change
global settings, a feed's URL or folder, or how often feeds are scanned: the API refuses those with
a 403, not just the UI. Everything else about a feed is theirs alone; see users.md.
Local sign-in at /login keeps working alongside the proxy, which is how you get in from the LAN
when the tunnel is down. So does the shared [web] token, which signs in as the admin and is the
way back in if you lock yourself out. A brand new database starts with admin / ipodderx;
change it.
Authentik in the request path instead
Not what ipodderx.sdf1.net uses, and not verified. Authentik can also sit in front of ipx
itself, with a Proxy Provider and an outpost that adds X-authentik-username:
- Applications → Providers → Create → Proxy Provider; mode Proxy (the outpost talks to ipx) or Forward auth (an existing reverse proxy asks the outpost).
- Applications → Create, bound to that provider, with a policy; add the provider to an outpost.
- In ipx:
trusted_header = "X-authentik-username", and the outpost's or reverse proxy's address intrusted_proxies. Measure that address as above rather than guessing it.
How this is secured
The header is only believed from trusted_proxies. Every other source is ignored, and the
request falls through to a session cookie or the shared token. That is the whole security boundary.
With the tunnel reaching ipx through the host's port, 192.168.16.1 means any container on Tower
that connects to 192.168.1.130:8099, not only cloudflared. Machines on the LAN, and Tower
itself, arrive under their own addresses and cannot set the header; the checks above show both
sides. Never list a LAN address or range: anyone there could then send
Cf-Access-Authenticated-User-Email: rays@sdf1.net and be you.
What ipx does not do: it does not verify Cloudflare's signed Cf-Access-Jwt-Assertion. It
trusts the hop. Verifying the signature would make the containers on Tower irrelevant to the
boundary, and is the upgrade if that ever matters.
Turning it off: clear trusted_header and restart. Proxy-made accounts stay, but nobody can sign
in with them until they are given a password (ipx user passwd <name>).
Everyday administration
ipx user list # who exists, how each signs in, and when
echo -n 'secret123' | ipx user add sam # local account, password on stdin
ipx user add sam@example.com --no-password # proxy-only account, made ahead of time
ipx user rename sam sam@example.com # give an account the name the proxy sends
echo -n 'newsecret' | ipx user passwd sam # change a password
ipx user rm sam # remove the account
In the container, put docker exec iPodderX in front, and docker exec -i iPodderX for the ones
that read a password.
Set auto_create_users = false once everyone who should have an account has one. After that the
proxy vouching for an unknown name is logged and refused. Make people ahead of time instead, with
the exact name the header will carry.
See also users.md for what several people share, configuration.md
for every [web] key, and cli.md for the ipx user commands.