How nginx adds custom CSS to Authelia when asset_path cannot

Short answer: Authelia’s server.asset_path replaces favicon.ico, logo.png, and locale JSON. It has no stylesheet. The portal’s default Content-Security-Policy is style-src 'self' 'nonce-${NONCE}', so an off-origin CSS URL is refused, and an injected <style> cannot see the per-request nonce. The theme belongs on the reverse proxy: nginx sub_filter inserts a same-origin <link>, and that file overrides the portal’s CSS variables. Those files are not in the Authelia image, so an image upgrade does not delete them.


What the login page actually sends

The first response is HTML: a </head>, one bundled stylesheet, and data-theme on the document (light, dark, grey, or oled). theme: auto follows prefers-color-scheme. Dark and OLED tokens set a light --foreground and a white --custom-icon. The sign-in glyph is an SVG filled with that variable.

Unless you change server.headers.csp_template, the documented default is style-src 'self' 'nonce-${NONCE}'. 'self' allows a stylesheet from the portal host. The nonce allows Authelia’s own inline styles. sub_filter is a fixed-string replace, so it cannot copy that nonce onto a tag you insert.

That rules out editing assets baked into the image, and it rules out a <style> block in the HTML you inject.


What asset_path can and cannot do

Authelia serves static files from an embedded filesystem. server.asset_path overlays a directory. The Server Asset Overrides guide lists three entries: favicon.ico, logo.png, and locales/<lang>/<namespace>.json. There is no CSS file in that list.

So these do not theme the page:

  • A stylesheet dropped into the asset directory
  • theme: light as if a background image were a theme option — theme only selects the built-in token set
  • Relaxing csp_template so a third-party theme can load. The server docs call that header security-critical and say it should almost never be configured

The working path

  1. Leave Authelia’s HTML, theme, and CSP alone.
  2. On the proxy location for the portal, clear Accept-Encoding, set proxy_buffering on, and replace the first </head> with a same-origin stylesheet link. nginx -V must show --with-http_sub_module (the module is not in every build). sub_filter does not edit a gzipped body, and it does not edit an unbuffered one.
  3. Serve that CSS, and any images it references, from the same host. Cache the file, and change the query string on the <link> when the CSS changes.
  4. In the stylesheet, set color-scheme: light and override the tokens the portal reads — --background, --foreground, --card, --custom-icon, --primary, and the matching foreground and border tokens — on :root and on each [data-theme], with !important. Paint html, body, and .bg-background, because the shell covers the body. Hang a corner drawing on body::before (position: fixed, pointer-events: none) so it stays out of the centered card and does not take clicks.
proxy_set_header Accept-Encoding "";
proxy_buffering on;
sub_filter '</head>' '<link rel="stylesheet" href="/portal-theme/portal.css"></head>';
sub_filter_once on;

Nothing in the directory has to change. asset_path can still replace the favicon and the logo beside this.

This only restyles HTML the proxy returns for the portal. Applications behind forward-auth keep their own CSS. Responses that are not text/html are not rewritten.


Approaches that look simpler and fail

Approach Why it fails if the portal must keep the default CSP
CSS under asset_path The documented overrides are the favicon, the logo, and locale JSON
An inline <style> via sub_filter The nonce is per request, and sub_filter cannot copy it
A third-party theme URL (the theme-park nginx snippet) style-src 'self' refuses an off-origin stylesheet
Override only --background Dark tokens keep light text and a white --custom-icon
Leave gzip on, or proxy_buffering off sub_filter needs a buffered, uncompressed HTML body

A third-party theme is a reasonable look for a portal whose CSP you are willing to widen. It is the wrong tool when the default policy stays.


When you can drop the proxy stylesheet

Drop it when you give up “a look the built-in themes do not have”:

  • A logo, a favicon, or translated strings — asset_path is the supported hook
  • The built-in light, dark, grey, or OLED theme
  • A later Authelia release that documents a real stylesheet hook
  • A deliberate edit to csp_template so off-origin CSS is allowed

Those are product or policy changes, not a hidden switch in theme. Until one of them is true, keep the stylesheet on the proxy host, not in the container filesystem the next image pull replaces. After an upgrade, recheck only if the portal no longer emits </head>, 'self' is gone from style-src, or the variable names move. The files themselves are still there.


Why this stays published

We used this on a homelab portal so the login page could carry the same paper ground as an internal page, without forking Authelia. The same gap shows up wherever a portal has no stylesheet hook and a nonce CSP. A custom Authelia login theme is a same-origin stylesheet on the reverse proxy. It is not an asset_path file, and replacing the Authelia image does not override it.

本文由 HoHo 與 AI 協作整理,最後更新於 2026 年 10 月 9 日。

Leave a Reply

Your email address will not be published. Required fields are marked *