# Hermes WebUI Authentication Methods: Password, Passkeys, and HMAC Cookies Explained

> Explore Hermes WebUI authentication: password hashing, WebAuthn passkeys, and HMAC cookies. Secure your access with these robust methods.

- Repository: [Nathan Esquenazi/hermes-webui](https://github.com/nesquena/hermes-webui)
- Tags: deep-dive
- Published: 2026-06-01

---

**Hermes WebUI supports three authentication mechanisms: PBKDF2-SHA256 password hashing, optional WebAuthn passkeys, and HMAC-signed session cookies that maintain state after initial login.**

The `nesquena/hermes-webui` repository implements a flexible authentication layer that protects both the web interface and API endpoints. Understanding these Hermes WebUI authentication methods is essential for securing deployments in production environments. The system treats passwords, passkeys, and session tokens as complementary components of a unified security model.

## Understanding Hermes WebUI Authentication Architecture

The authentication system centers on [`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py), which coordinates multiple verification strategies through a common interface.

### Auth Enablement Logic

The `is_auth_enabled()` function returns `True` when either password authentication or passkey support is active. As implemented in [`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 53-55, this check gates access to protected resources by verifying `is_password_auth_enabled()` **OR** `are_passkeys_enabled()`.

Password hashes resolve through `get_password_hash()` ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 92-97), which prioritizes the `HERMES_WEBUI_PASSWORD` environment variable before falling back to [`settings.json`](https://github.com/nesquena/hermes-webui/blob/main/settings.json). The implementation caches PBKDF2-SHA256 computations to avoid the approximately one-second hashing cost on every request.

### Session Management Overview

After successful authentication via any method, `create_session()` generates a 64-character hex token, attaches an expiry timestamp, and signs the payload using `_signing_key()` ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 89-97). The `set_auth_cookie()` function ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 72-82) then transmits this as an HttpOnly, SameSite-Lax cookie named `hermes_session`.

## Password Authentication

Password authentication uses PBKDF2-SHA256 hashing with 600,000 iterations. The system stores only the hash, never the plaintext password.

### Configuration Methods

You can configure password authentication via environment variable or configuration file:

```python

# Environment variable (recommended for containerized deployments)

export HERMES_WEBUI_PASSWORD='MyStrongPassword'

# Or generate a PBKDF2 hash for settings.json

python -c "import hashlib,os,base64; print(hashlib.pbkdf2_hmac('sha256','MyStrongPassword'.encode(),os.urandom(32),600_000).hex())"

```

The `_resolve_session_ttl()` and `get_password_hash()` functions ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 29-34 and 92-97) handle this resolution, reading the environment first and caching results after the initial computation.

### API Login Endpoint

Authenticate programmatically to receive the HMAC-signed session cookie:

```bash
curl -X POST -H "Content-Type: application/json" \
     -d '{"password":"MyStrongPassword"}' \
     http://localhost:8789/api/auth/login -c cookies.txt

```

This request triggers `check_auth()` in [`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 89-101, which validates the PBKDF2 hash and establishes the session.

## Passkey (WebAuthn) Authentication

Passkey support provides phishing-resistant authentication using hardware or platform authenticators. This feature requires explicit opt-in through a feature flag.

### Feature Flag and Availability

The `_passkey_feature_flag_enabled()` function ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 108-119) checks for `HERMES_WEBUI_PASSKEY=1` or the `webui_passkey_enabled` configuration key. However, the flag alone does not enable passkey login—`are_passkeys_enabled()` ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 140-147) also verifies that at least one credential exists in [`passkeys.json`](https://github.com/nesquena/hermes-webui/blob/main/passkeys.json).

Enable passkey support globally or per-profile:

```bash

# Global environment variable

export HERMES_WEBUI_PASSKEY=1

# Or in config.yaml

webui_passkey_enabled: true

```

### Registration Flow

The [`api/passkeys.py`](https://github.com/nesquena/hermes-webui/blob/main/api/passkeys.py) file handles WebAuthn ceremonies. Registration begins with a challenge from `/api/auth/passkey/options` and concludes with `finish_registration()` storing the credential in [`passkeys.json`](https://github.com/nesquena/hermes-webui/blob/main/passkeys.json) (lines 102-104).

```javascript
// Frontend registration example
fetch('/api/auth/passkey/options', {method: 'POST'})
  .then(r => r.json())
  .then(opts => navigator.credentials.create({publicKey: opts}))
  .then(cred => fetch('/api/auth/passkey/register', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({response: cred, label: 'My-YubiKey'})
      }));

```

### Passkey Login

Existing passkey users authenticate through the WebAuthn `get` ceremony:

```javascript
fetch('/api/auth/passkey/options', {method: 'POST'})
  .then(r => r.json())
  .then(opts => navigator.credentials.get({publicKey: opts}))
  .then(assertion => fetch('/api/auth/passkey/login', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({response: assertion})
      }));

```

Success returns the same HMAC-signed `hermes_session` cookie used for password logins.

## HMAC-Signed Session Cookies

The session cookie binds the authentication state to subsequent requests without requiring repeated password entry or passkey verification.

### Cookie Attributes and Security

The `set_auth_cookie()` function ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 72-82) configures the cookie with:
- **Name**: `hermes_session`
- **HttpOnly**: Prevents JavaScript access
- **SameSite**: Lax
- **Secure**: Enabled when appropriate
- **Max-Age**: Derived from `_resolve_session_ttl()`

### Session Verification

Each protected request invokes `verify_session()` ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 110-123), which:
1. Validates the HMAC signature against the server-side key
2. Prunes expired entries from the session store
3. Confirms the token exists and is active

If verification fails, `check_auth()` returns HTTP 401 for API calls or redirects to the login page for UI routes.

### Using Session Cookies

After authentication, include the cookie in subsequent API requests:

```bash
curl -b cookies.txt http://localhost:8789/api/agents

```

The server validates this cookie through `verify_session()` before serving protected content.

## Summary

- **Password Authentication**: Uses PBKDF2-SHA256 hashing via `HERMES_WEBUI_PASSWORD` env var or [`settings.json`](https://github.com/nesquena/hermes-webui/blob/main/settings.json), implemented in [`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 92-97.
- **Passkey Authentication**: WebAuthn support controlled by `HERMES_WEBUI_PASSKEY` flag and credential existence checks in [`api/passkeys.py`](https://github.com/nesquena/hermes-webui/blob/main/api/passkeys.py) and [`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 108-147.
- **Session Management**: HMAC-signed cookies (`hermes_session`) created by `create_session()` and verified by `verify_session()` in [`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py), with HttpOnly and SameSite-Lax security attributes.
- **Unified Protection**: The `is_auth_enabled()` check in [`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) coordinates all methods, while `check_auth()` gates every request.

## Frequently Asked Questions

### How do I enable password-only authentication without passkeys?

Set the `HERMES_WEBUI_PASSWORD` environment variable or add a PBKDF2-SHA256 hash to [`settings.json`](https://github.com/nesquena/hermes-webui/blob/main/settings.json). Do not set `HERMES_WEBUI_PASSKEY=1`. The `is_auth_enabled()` function will return `True` based on `is_password_auth_enabled()` while `are_passkeys_enabled()` remains `False`.

### What happens if I enable passkeys but have no registered credentials?

The passkey authentication option remains unavailable. The `are_passkeys_enabled()` function ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 140-147) requires both the feature flag (`_passkey_feature_flag_enabled()`) AND at least one credential stored in [`passkeys.json`](https://github.com/nesquena/hermes-webui/blob/main/passkeys.json). Users must first register a passkey through the registration flow before the login option appears.

### Are session cookies vulnerable to tampering?

No. The `hermes_session` cookie contains an HMAC signature generated by `_signing_key()` and verified by `verify_session()` ([`api/auth.py`](https://github.com/nesquena/hermes-webui/blob/main/api/auth.py) lines 110-123). Without the server-side signing key, clients cannot forge valid session tokens. The cookie also uses HttpOnly and Secure flags to prevent XSS and interception attacks.

### Can I use password and passkey authentication simultaneously?

Yes. The authentication system supports both methods concurrently. `is_auth_enabled()` activates when either method is configured, and users can choose their preferred authentication mechanism at login. Both successful password logins and successful passkey assertions receive identical HMAC-signed session cookies for subsequent request authorization.