Files
web_template/AUTHELIA_INTEGRATION.md
jared f61705afb8
Lint / JS (eslint) (push) Successful in 12s
docs: add Authelia portal integration guide
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:30:39 -04:00

18 KiB
Raw Permalink Blame History

Authelia Portal — LotusGuild Terminal Design System Integration

This document covers everything that had to be written from scratch to theme the Authelia authentication portal using the LotusGuild Terminal Design System v1.2. It is a companion to base.css / base.js for future integrations with third-party applications that use their own component frameworks.


Why a separate file at all?

base.css targets exclusively .lt-* class names — the design system's own prefix. Authelia's frontend is a React single-page application built on Material UI (MUI) v5, which generates its own stable class names like .MuiCard-root, .MuiButton-containedPrimary, etc. Bridging the two requires a translation layer: the same design tokens and visual patterns from base.css, re-expressed against MUI's class structure.

Additionally, Authelia v4.x (latest: 4.39.x) does not support native CSS injection — server.asset_path only handles logo.png and favicon.ico. The CSS is injected via nginx sub_filter in Nginx Proxy Manager, which rewrites Authelia's HTML on the fly to include a <link> tag before </head>.


Deployment architecture

Browser
  └─► https://auth.lotusguild.org  (NPM — LXC 139)
        ├─ GET /custom.css  ──────── served from /data/custom_assets/authelia-custom.css
        │                            (nginx alias, no upstream request)
        └─ GET /*  ──────────────── proxied to Authelia (LXC 167 :9091)
                                     HTML response is rewritten by sub_filter:
                                     </head>  →  <link rel="stylesheet" href="/custom.css"></head>

NPM config changed: /data/nginx/proxy_host/29.conf (auth.lotusguild.org proxy host)

# Serve LotusGuild Terminal custom CSS
location = /custom.css {
    alias /data/custom_assets/authelia-custom.css;
    add_header Content-Type "text/css";
    add_header Cache-Control "public, max-age=3600";
}

location / {
    ...existing headers...

    # Prevent upstream gzip so sub_filter can inspect the body
    proxy_set_header Accept-Encoding "";

    # Inject CSS link before </head>
    sub_filter '</head>' '<link rel="stylesheet" href="/custom.css"></head>';
    sub_filter_once on;

    include /etc/nginx/conf.d/include/proxy.conf;
}

Warning: NPM regenerates proxy_host conf files when you edit a proxy host in the UI. If host 29 is ever modified through NPM, re-apply the sub_filter block and the /custom.css location manually, or set the config via NPM's "Advanced" tab before saving.

Authelia config changed: /etc/authelia/configuration.yml — added asset_path under server: so Authelia serves the resized logo and favicon:

server:
  address: tcp://0.0.0.0:9091
  asset_path: /etc/authelia/assets

Assets in /etc/authelia/assets/:

  • logo.png — 256 × 256 PNG resized from Tinker Tickets assets/images/favicon.png
  • favicon.ico — 32 × 32 ICO from the same source

What is NOT in base.css and had to be written

The sections below are ordered as they appear in authelia-custom.css.


1. Re-declaring design tokens with !important

In base.css: :root { ... } is declared once, without !important.

What had to be added: The entire :root block is repeated in custom.css because Authelia's HTML is a self-contained SPA — base.css is never loaded. More critically, MUI injects its own inline styles and CSS-in-JS rules at very high specificity. Every property that needs to override MUI requires !important. base.css never uses !important because it owns its namespace; here we are guests in MUI's DOM.

/* base.css does NOT use !important anywhere */
body { background-color: var(--bg-primary); }

/* custom.css must fight MUI's inline styles */
body { background-color: var(--bg-primary) !important; }

The token values themselves are identical to base.css — only the override mechanism is new.


2. Universal box-sizing reset with !important

In base.css: *, *::before, *::after { box-sizing: border-box; } — no flag needed because base.css loads first.

What had to be added:

*, *::before, *::after {
  box-sizing: border-box !important;
}

MUI components set box-sizing inline on some elements. Without the flag the clip-path geometry breaks on inputs and buttons.


3. MUI typography selectors

In base.css: Typography rules target h1h6, a, p, body — standard HTML elements. No MUI class names exist in the design system.

What had to be added: Authelia renders text almost exclusively through MUI's <Typography> component, which produces elements with .MuiTypography-root and variant classes. Without targeting these, all text inherits MUI's Roboto font and light grey colour instead of JetBrains Mono and --text-primary.

.MuiTypography-root,
.MuiInputBase-input,
.MuiFormLabel-root,
.MuiFormHelperText-root,
label, p, span, div {
  font-family: var(--font-mono) !important;
  color: var(--text-primary) !important;
}

h1, h2, h3, h4, h5, h6,
.MuiTypography-h5,
.MuiTypography-h6 {
  color: var(--accent-orange) !important;
  text-shadow: var(--glow-orange) !important;
  ...
}

4. .MuiCard-root / .MuiPaper-root — the login card

In base.css: .lt-card implements the terminal card with clip-path, border, background, and the ::before corner triangle accent.

What had to be added: Authelia wraps its login form in a MUI <Card> (which extends <Paper>). Neither selector exists in base.css. The visual result is identical to .lt-card — the clip-path polygon, cyan border, box-glow, and corner triangle are all copied — but they target different class names and require !important throughout to override MUI's elevation shadows and border-radius: 4px.

.MuiCard-root,
.MuiPaper-root {
  background: var(--bg-card) !important;
  border: 1px solid var(--border-color) !important;
  border-radius: 0 !important;           /* ← overrides MUI default */
  clip-path: polygon(...) !important;    /* same geometry as .lt-card */
  ...
}

.MuiCard-root::before,
.MuiPaper-root::before {
  /* same corner triangle as .lt-card::before */
}

5. Logo element — img[alt="Authelia"]

In base.css: No equivalent. The design system has no logo slot component.

What had to be added: Authelia renders the portal logo as <img alt="Authelia" src="./static/media/logo.png">. This selector targets it specifically to apply the cyan drop-shadow and orange hover glow. Without it the logo renders without any terminal aesthetic treatment.

img[alt="Authelia"] {
  filter: drop-shadow(0 0 12px rgba(0,212,255,0.4)) !important;
  transition: filter 0.25s ease !important;
}
img[alt="Authelia"]:hover {
  filter: drop-shadow(0 0 18px rgba(255,107,0,0.5)) !important;
}

6. MUI outlined input family

In base.css: .lt-input, .lt-select, .lt-textarea — custom elements with clip-path, terminal background, and cyan focus ring.

What had to be added: MUI's outlined text field is a composition of three separate elements that each need individual targeting:

MUI class What it controls Equivalent in base.css
.MuiOutlinedInput-root Outer wrapper — background, clip-path .lt-input outer
.MuiOutlinedInput-notchedOutline The SVG border element .lt-input border
.MuiInputBase-input The actual <input> inside .lt-input inner text
.MuiInputLabel-root Floating label .lt-label
.MuiFormHelperText-root Error / hint text below field .lt-form-hint

MUI splits the border rendering into a separate SVG <fieldset> element (.MuiOutlinedInput-notchedOutline), which requires its own border-color override. base.css has no equivalent splitting since .lt-input is a single element with a CSS border.

The .Mui-focused and .Mui-error state classes also need explicit overrides because MUI applies its own colour through CSS-in-JS with high specificity.

.MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline {
  border-color: var(--accent-cyan) !important;
  box-shadow: var(--box-glow-cyan) !important;
}
.MuiFormHelperText-root.Mui-error {
  color: var(--accent-red) !important;
  text-shadow: var(--glow-red) !important;
}

Additionally, caret-color is set explicitly on the input because MUI does not expose this through its theme — the blinking text cursor would otherwise be white.


7. MUI button family

In base.css: .lt-btn, .lt-btn-primary, .lt-btn-danger, .lt-btn-ghost — all using clip-path hexagon cuts, transparent backgrounds, and border+glow styling.

What had to be added: MUI uses four separate button variant classes. The primary (sign-in) button is .MuiButton-containedPrimary which by default renders as a solid filled rectangle. The entire button appearance — transparent background, border, clip-path, text-transform, letter-spacing — has to be explicitly overridden because MUI's contained variant applies an opaque background-color inline.

/* MUI default: background-color: #1976d2; border-radius: 4px; box-shadow: ... */
.MuiButton-containedPrimary {
  background: transparent !important;   /* fight the inline fill */
  border: 1px solid var(--accent-orange-border) !important;
  border-radius: 0 !important;
  clip-path: polygon(...) !important;
  ...
}

Text/link buttons (.MuiButton-text) are separate from contained buttons in MUI but map to the same .lt-btn-ghost pattern in base.css.


8. MUI checkbox and switch

In base.css: .lt-checkbox, .lt-switch — fully custom-drawn using CSS only.

What had to be added: Authelia uses MUI's <Checkbox> (remember-me) and <Switch> (settings toggles). These are SVG-based components. Because MUI controls their colour through injected CSS variables, the only reliable override is to target .Mui-checked state classes and use !important. There is no equivalent split between track and thumb in base.css.

.MuiSwitch-switchBase.Mui-checked { color: var(--accent-cyan) !important; }
.MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track {
  background-color: var(--accent-cyan) !important;
  opacity: 0.5 !important;
}

9. MUI alert / notification banners

In base.css: .lt-inline-* for inline messages (.lt-inline-success, .lt-inline-error, etc.) and .lt-toast-* for toasts. These use a left border accent pattern.

What had to be added: Authelia uses MUI <Alert> for login errors, success messages, and 2FA prompts. MUI Alert has its own severity system with four variant classes (.MuiAlert-standardError, etc.). Each severity needs explicit colour, background, and border-color overrides. The border-left: 3px solid currentColor pattern is adapted from base.css's inline message style but applied to MUI's class names with clip-path: none !important to cancel MUI's rounded corners.


10. MUI select, menu, and popover

In base.css: .lt-select for the select element itself; no dropdown overlay component exists since the design system uses native <select>.

What had to be added: MUI's <Select> renders its dropdown as a <Paper> element portalled to <body> with class .MuiMenu-paper. Without targeting this separately, the dropdown popup inherits the browser default white background and Roboto font, appearing completely unstyled. .MuiMenuItem-root hover and selected states also need individual rules.


11. OTP / 2FA digit inputs — input[type="tel"], input[type="number"]

In base.css: No equivalent. The design system has no numeric code input.

What had to be added: Authelia's one-time-password entry uses <input type="tel"> elements arranged in a row. These need oversized, centered, cyan-glowing characters with wide letter-spacing to render like a retro code display.

input[type="tel"],
input[type="number"] {
  font-size: 1.2rem !important;
  font-weight: 700 !important;
  color: var(--accent-cyan) !important;
  text-shadow: var(--glow-cyan) !important;
  letter-spacing: 0.15em !important;
  text-align: center !important;
  caret-color: var(--accent-orange) !important;
}

12. MUI stepper — 2FA flow breadcrumb

In base.css: .lt-stepper, .lt-step, .lt-step-num — a custom CSS stepper for multi-step wizard flows.

What had to be added: Authelia renders a MUI <Stepper> at the top of the 2FA flow showing steps like "Username → Password → TOTP". MUI Stepper uses distinct classes for label, icon, and connector that don't map 1-to-1 to .lt-step-*. The step icon is an SVG circle rendered by React so it can only be tinted via color and filter: drop-shadow(...). The connector line between steps is a separate <hr> with class .MuiStepConnector-line.

.MuiStepIcon-root.Mui-active {
  color: var(--accent-orange) !important;
  filter: drop-shadow(0 0 6px rgba(255,107,0,0.6)) !important;
}
.MuiStepIcon-root.Mui-completed {
  color: var(--accent-green) !important;
}

13. MUI linear and circular progress

In base.css: .lt-progress / .lt-progress-bar — a standard CSS progress bar using width transitions. .lt-spinner — a rotating pseudo-element spinner.

What had to be added: Authelia uses MUI <LinearProgress> during API calls and <CircularProgress> as the main loading spinner. MUI Linear Progress animates its own internal elements (.MuiLinearProgress-bar) with keyframe animations; it cannot be replaced with the .lt-progress pattern. The cyan-to-orange gradient and box-shadow glow are new here — base.css's progress bar uses a solid --accent-orange fill.

.MuiLinearProgress-bar {
  background: linear-gradient(90deg, var(--accent-cyan), var(--accent-orange)) !important;
  box-shadow: 0 0 8px rgba(0,212,255,0.5) !important;
}

14. MUI tooltip

In base.css: .lt-tooltip — a CSS-only tooltip shown via :hover on a [data-tooltip] attribute.

What had to be added: MUI <Tooltip> portals its bubble to <body> with class .MuiTooltip-tooltip. It is a separate DOM node, not a pseudo-element, so .lt-tooltip styles cannot reach it. A standalone rule sets the terminal background, monospace font, and squared border.


15. *:focus-visible global ring

In base.css: Focus rings are defined individually per component (.lt-btn:focus-visible, .lt-input:focus-visible, etc.).

What had to be added: A single global rule is more practical here because Authelia contains many interactive MUI elements not known at design time, and adding per-component rules for every possible focusable element would be brittle.

*:focus-visible {
  outline: 2px solid var(--accent-cyan) !important;
  outline-offset: 2px !important;
  box-shadow: var(--box-glow-cyan) !important;
}

In base.css: No equivalent. The design system does not add page-level watermarks.

What had to be added: Authelia has no footer. The #root::after pseudo-element attaches a fixed-position text watermark reading LOTUSGUILD SECURE PORTAL // AUTH.LOTUSGUILD.ORG at the bottom of the viewport. This uses --text-muted, --font-mono, uppercase, and wide letter-spacing to match the terminal aesthetic without adding any HTML.

#root::after {
  content: 'LOTUSGUILD SECURE PORTAL // AUTH.LOTUSGUILD.ORG';
  position: fixed;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  color: var(--text-muted);
  opacity: 0.6;
  pointer-events: none;
}

Patterns shared with base.css (not new)

The following are not documented above because they are direct re-uses of existing base.css patterns, only re-targeted to new selectors:

Pattern base.css source Re-used in custom.css for
Dot-grid background html { background-image: radial-gradient(...) } body
Scanlines overlay body::before body::before (same rule, !important added)
Corner vignette body::after body::after (same rule, !important added)
Card clip-path polygon .lt-card { clip-path: polygon(...) } .MuiCard-root
Card corner triangle .lt-card::before .MuiCard-root::before
Button clip-path hexagon .lt-btn { clip-path: polygon(...) } .MuiButton-containedPrimary
Input clip-path cut .lt-input { clip-path: polygon(...) } .MuiOutlinedInput-root
Scrollbar ::-webkit-scrollbar-* section 45 identical, border-radius: 0 added
Link colours a { color: var(--accent-cyan) } a (same, !important added)
Divider .lt-divider border-color .MuiDivider-root

Notes for future third-party integrations

  1. Always check for !important requirements. CSS-in-JS frameworks (MUI, Emotion, styled-components) inject styles at runtime with high specificity. Without !important, most design token overrides will lose to injected inline styles.

  2. The split border problem. MUI (and many component libraries) separate the border from the element it visually belongs to. Always inspect the actual DOM before writing selectors — what looks like one element is often three.

  3. Portalled overlays. Dropdowns, tooltips, and modals in MUI are rendered into a separate DOM tree (<body> portal). Scoped selectors inside a card or form won't reach them. Target their root classes (.MuiMenu-paper, .MuiTooltip-tooltip) directly.

  4. State classes. MUI uses .Mui-focused, .Mui-checked, .Mui-error, .Mui-active, .Mui-completed, .Mui-disabled as state modifiers. These are the equivalent of :focus, :checked, etc. but applied by JavaScript — pseudo-class selectors alone will sometimes not fire.

  5. SVG-based components. Checkboxes, radio buttons, step icons, and spinners are SVGs injected by React. They cannot be styled with background, border, or clip-path. Use color (SVG currentColor inheritance) and filter: drop-shadow() instead.