Summary
Add support for external authentication systems (e.g., Authelia, Authentik, Keycloak, OAuth2-Proxy) to authenticate users and optionally bind them to specific Hermes profiles via HTTP headers. This enables multi-user deployments where different users automatically land in different profiles without managing separate passwords or login flows within Hermes WebUI itself.
Motivation
Currently, Hermes WebUI supports:
- Single shared password (
HERMES_WEBUI_PASSWORD)
- Passkeys (WebAuthn)
Both require users to authenticate directly with the WebUI. In deployments where a reverse proxy already handles authentication (Authelia, Authentik, etc.), users must authenticate twice: once with the proxy, once with the WebUI. This is friction-heavy and prevents automatic profile assignment based on the user's identity or group membership.
Use Case
A self-hosted Hermes instance is shared among:
- Person A (admin) — needs access to all profiles
- Person B (DevOps team) — should only see the
devops profile
- Person C (coworker) — should only see the
coworkers profile
The reverse proxy (with Authelia) already knows who the user is and what groups they belong to. The WebUI should trust this information and route the user to the correct profile automatically.
Proposed Solution
1. Environment Variables
# Required: Header containing the authenticated username
HERMES_WEBUI_TRUSTED_AUTH_HEADER=Remote-User
# Optional: Header containing comma-separated group list
HERMES_WEBUI_TRUSTED_GROUPS_HEADER=Remote-Groups
# Optional: JSON mapping of group names to Hermes profile names
HERMES_WEBUI_GROUP_PROFILE_MAP='{"hermes_devops":"devops","hermes_coworkers":"coworkers"}'
# Optional: URL to redirect for logout (e.g., Authelia logout)
HERMES_WEBUI_TRUSTED_AUTH_LOGOUT_URL=https://auth.example.com/logout
2. Authentication Flow
Browser → Reverse Proxy (Authelia) → Hermes WebUI
│ │
│ Remote-User: alice │
│ Remote-Groups: ai_users,hermes_devops
│ │
└───────────────────────────┘
│
▼
1. Read trusted headers
2. Resolve profile from group mapping
3. Create session (auto-login)
4. Set cookies: hermes_session + hermes_profile
5. User lands in bound profile
3. Profile Resolution Logic
def resolve_profile_from_headers(headers):
user = headers.get('Remote-User')
if not user:
return None # No trusted auth, fall through to password/passkey
groups = headers.get('Remote-Groups', '').split(',')
group_map = json.loads(os.getenv('HERMES_WEBUI_GROUP_PROFILE_MAP', '{}'))
for group in groups:
if group in group_map:
return group_map[group] # e.g., "devops"
return None # No mapping matched → default profile
4. Session Binding
When a session is created via trusted header auth, it stores:
username: The authenticated user
profile: The resolved profile (or None for unrestricted access)
auth_type: "trusted"
On every subsequent request:
- Verify session cookie is valid
- If
session.profile is set, enforce that the hermes_profile cookie matches
- If the user tampered with the profile cookie → reject with 401
This prevents a user authenticated for devops from switching to coworkers by manipulating cookies.
5. Logout Behavior
| Auth Type |
Logout Action |
| Trusted Header (Authelia) |
Delete hermes_session + hermes_profile cookies, redirect to HERMES_WEBUI_TRUSTED_AUTH_LOGOUT_URL |
Password (HERMES_WEBUI_PASSWORD) |
Delete cookies, redirect to /login |
| Passkeys |
Delete cookies, redirect to /login |
| No auth enabled |
No logout button shown |
6. UI Changes
- Profile picker: Hidden or read-only when session is profile-bound
- Profiles panel: Shows only the bound profile, no switch option
- Logout button: Visible in settings/topbar when any auth is enabled
/api/auth/status: Returns trusted_auth_enabled, bound_profile, user
Example: Authelia + Docker Compose
Authelia Configuration
# configuration.yaml
access_control:
rules:
- domain: 'ai.example.com'
policy: one_factor
subject:
- 'group:ai_users'
# users_database.yaml
users:
alice:
displayname: "Alice Admin"
password: "$argon2id$..."
groups:
- ai_users
- hermes_devops # Dummy group → maps to "devops" profile
bob:
displayname: "Bob Coworker"
password: "$argon2id$..."
groups:
- ai_users
- hermes_coworkers # Dummy group → maps to "coworkers" profile
Docker Compose
services:
authelia:
image: authelia/authelia:latest
volumes:
- ./authelia:/config
# ...
hermes-webui:
image: ghcr.io/nesquena/hermes-webui:latest
environment:
HERMES_WEBUI_TRUSTED_AUTH_HEADER: Remote-User
HERMES_WEBUI_TRUSTED_GROUPS_HEADER: Remote-Groups
HERMES_WEBUI_GROUP_PROFILE_MAP: '{"hermes_devops":"devops","hermes_coworkers":"coworkers"}'
HERMES_WEBUI_TRUSTED_AUTH_LOGOUT_URL: https://auth.example.com/logout
# Password auth disabled — Authelia handles everything
# HERMES_WEBUI_PASSWORD: ""
labels:
- "traefik.http.routers.hermes.middlewares=authelia@docker"
Dummy Groups Pattern
The proposed design uses dummy groups in Authelia that serve no purpose other than profile mapping:
| Authelia Group |
Hermes Profile |
Purpose |
hermes_devops |
devops |
Routes DevOps users to devops profile |
hermes_coworkers |
coworkers |
Routes coworkers to coworkers profile |
ai_users |
— |
Controls access to the WebUI itself |
This avoids polluting functional groups (like admins, solar_users) with Hermes-specific concerns.
Security Considerations
| Concern |
Mitigation |
| Header spoofing |
Headers are only trusted from localhost/reverse proxy; never exposed to the internet directly |
| Cookie tampering |
Session stores bound profile; cookie mismatch → 401 |
| Session fixation |
New session created on each trusted-auth login; old sessions invalidated on logout |
| No auth bypass |
If HERMES_WEBUI_TRUSTED_AUTH_HEADER is set but missing in request → fall through to password or 401 |
Backwards Compatibility
- Fully backwards compatible: If no
HERMES_WEBUI_TRUSTED_AUTH_HEADER is set, behavior is unchanged
- Existing
HERMES_WEBUI_PASSWORD continues to work as fallback
- Passkeys continue to work independently
Related Issues
Implementation Notes
The implementation touches:
api/auth.py — Trusted header resolution, session binding
api/routes.py — /api/auth/logout endpoint, /api/auth/status extension
server.py — Cookie handling for trusted-auth sessions
static/ui.js / static/panels.js — Logout button, profile picker visibility
static/index.html — Logout button placement
Estimated effort: ~150 lines backend + ~50 lines frontend + tests.
Checklist
Summary
Add support for external authentication systems (e.g., Authelia, Authentik, Keycloak, OAuth2-Proxy) to authenticate users and optionally bind them to specific Hermes profiles via HTTP headers. This enables multi-user deployments where different users automatically land in different profiles without managing separate passwords or login flows within Hermes WebUI itself.
Motivation
Currently, Hermes WebUI supports:
HERMES_WEBUI_PASSWORD)Both require users to authenticate directly with the WebUI. In deployments where a reverse proxy already handles authentication (Authelia, Authentik, etc.), users must authenticate twice: once with the proxy, once with the WebUI. This is friction-heavy and prevents automatic profile assignment based on the user's identity or group membership.
Use Case
A self-hosted Hermes instance is shared among:
devopsprofilecoworkersprofileThe reverse proxy (with Authelia) already knows who the user is and what groups they belong to. The WebUI should trust this information and route the user to the correct profile automatically.
Proposed Solution
1. Environment Variables
2. Authentication Flow
3. Profile Resolution Logic
4. Session Binding
When a session is created via trusted header auth, it stores:
username: The authenticated userprofile: The resolved profile (orNonefor unrestricted access)auth_type:"trusted"On every subsequent request:
session.profileis set, enforce that thehermes_profilecookie matchesThis prevents a user authenticated for
devopsfrom switching tocoworkersby manipulating cookies.5. Logout Behavior
hermes_session+hermes_profilecookies, redirect toHERMES_WEBUI_TRUSTED_AUTH_LOGOUT_URLHERMES_WEBUI_PASSWORD)/login/login6. UI Changes
/api/auth/status: Returnstrusted_auth_enabled,bound_profile,userExample: Authelia + Docker Compose
Authelia Configuration
Docker Compose
Dummy Groups Pattern
The proposed design uses dummy groups in Authelia that serve no purpose other than profile mapping:
hermes_devopsdevopshermes_coworkerscoworkersai_usersThis avoids polluting functional groups (like
admins,solar_users) with Hermes-specific concerns.Security Considerations
HERMES_WEBUI_TRUSTED_AUTH_HEADERis set but missing in request → fall through to password or 401Backwards Compatibility
HERMES_WEBUI_TRUSTED_AUTH_HEADERis set, behavior is unchangedHERMES_WEBUI_PASSWORDcontinues to work as fallbackRelated Issues
Implementation Notes
The implementation touches:
api/auth.py— Trusted header resolution, session bindingapi/routes.py—/api/auth/logoutendpoint,/api/auth/statusextensionserver.py— Cookie handling for trusted-auth sessionsstatic/ui.js/static/panels.js— Logout button, profile picker visibilitystatic/index.html— Logout button placementEstimated effort: ~150 lines backend + ~50 lines frontend + tests.
Checklist