Skip to content

WebUI authentication broken through Cloudflare proxy with strict browsers (e.g. Librewolf) #2096

Description

@Tiritibambix

Hi,

I recently migrated from tomsquest/docker-radicale to the official ghcr.io/kozea/radicale:stable image and ran into an issue with the WebUI getting stuck on the loading spinner when accessed through a reverse proxy (Nginx Proxy Manager) with Cloudflare proxy enabled.

After a lot of debugging, I found that the new WebUI introduced in 3.7.0 handles authentication via XHR requests in JavaScript, whereas the older WebUI triggered a native browser Basic Auth popup. This change in behavior causes two related issues.

First, when Cloudflare proxy is active, it modifies response headers in transit, stripping or altering the CORS and WWW-Authenticate headers that the WebUI JavaScript needs to handle the 401 response and display the login form. This results in the WebUI being stuck on the spinner indefinitely. Adding Access-Control-Allow-Origin and related headers in the [headers] section of the Radicale config partially helps, but Cloudflare still interferes.

Second, privacy-hardened browsers like Librewolf are stricter about XHR-based Basic Auth challenges than standard Firefox or Chromium. With the old WebUI (native browser popup), Librewolf worked fine. With the new JS-based auth, Librewolf gets stuck on the spinner even when Cloudflare is bypassed — unless specific CORS headers are explicitly set in Radicale's config.

Disabling the Cloudflare proxy (DNS only mode) and adding the CORS headers to the Radicale config fixes everything for standard browsers. Librewolf works too once Cloudflare is out of the picture.

This might be worth documenting in the reverse proxy section, specifically that the new WebUI requires proper CORS headers and that Cloudflare proxy mode can break the authentication flow. A note about privacy-hardened browsers would also be helpful.

For reference, here is the [headers] config that made things work:

[headers]
Access-Control-Allow-Origin = https://your-domain.com
Access-Control-Allow-Methods = GET, POST, PUT, DELETE, PROPFIND, PROPPATCH, MKCALENDAR, MKCOL, REPORT, OPTIONS
Access-Control-Allow-Headers = Authorization, Content-Type, Depth, If-Match, If-None-Match
Access-Control-Expose-Headers = DAV, content-type, WWW-Authenticate

Thanks for the great work, the sharing feature is exactly what I needed.

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions