# ListMonk Admin API Security Considerations: Authentication, Authorization & Best Practices

> Secure your ListMonk admin API with bcrypt API keys, role-based permissions, and session cookies. Discover essential production security best practices like TLS and rate limiting.

- Repository: [Kailash Nadh/listmonk](https://github.com/knadh/listmonk)
- Tags: best-practices
- Published: 2026-05-19

---

**ListMonk protects its admin API using bcrypt-hashed API keys with constant-time comparison, role-based permissions, and session cookie fallback, but requires external TLS termination and rate limiting for production security.**

The **ListMonk** admin API provides programmatic access to manage campaigns, subscribers, lists, and system settings in the `knadh/listmonk` repository. Because this interface can modify critical data, understanding the security considerations for the admin API is essential before exposing it to external clients. The codebase implements multiple defensive layers—from cryptographic token validation to permission-based access control—while delegating transport security to the deployment environment.

## Authentication Mechanisms

ListMonk supports two primary authentication methods for the admin API: token-based API keys for programmatic access and session cookies for the web interface.

### Token-Based API Keys

API keys represent the primary authentication method for programmatic access. In [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go), the system stores API keys as **bcrypt hashes** in the `users` table, never in plain text. When a request arrives, the `parseAuthHeader` function extracts the `Authorization` header and expects the format `token <api_key>:<access_token>`.

The validation occurs in `GetAPIToken`, which uses **`subtle.ConstantTimeCompare`** to compare the provided token against the stored hash. This **constant-time comparison** prevents timing attacks that could leak information about valid credentials through response-time analysis.

```go
// From internal/auth/auth.go - conceptual flow
// 1. Parse header: "token <api_key>:<access_token>"
// 2. Lookup user by API key
// 3. Constant-time comparison of access token
if subtle.ConstantTimeCompare([]byte(token), []byte(userToken)) == 1 {
    // Authentication successful
}

```

### Session Cookie Fallback

For browser-based access to the web UI, the `Middleware` function in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go) falls back to cookie-based session validation when no `Authorization` header is present. The same authentication flow validates the session cookie against the database, ensuring consistent security controls across both API and web interfaces.

### Legacy Basic Auth Support

The codebase maintains backward compatibility with legacy `Basic` authentication. The `parseAuthHeader` routine also accepts `Authorization: Basic <base64>` headers, decoding the base64 string to extract the API key and access token pair.

## Authorization and Role-Based Access Control

After authentication, ListMonk enforces **role-based access control** (RBAC) through permission middleware.

### Permission Middleware

The `Perm` middleware in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go) inspects the authenticated user's `PermissionsMap` to verify they possess the required permission for the requested endpoint. Each admin API route specifies required permissions (e.g., `"campaigns:write"`, `"subscribers:read"`), and the middleware rejects requests from users lacking those permissions.

**Super-admin users**—identified by `SuperAdminRoleID` defined in [`internal/core/roles.go`](https://github.com/knadh/listmonk/blob/main/internal/core/roles.go)—bypass all permission checks entirely, granting them unrestricted access to the admin API.

### Implementing Permission Checks

When registering custom handlers in [`cmd/handlers.go`](https://github.com/knadh/listmonk/blob/main/cmd/handlers.go), developers can enforce permission requirements:

```go
func registerCustomRoutes(e *echo.Echo, auth *auth.Auth) {
    // Only users with "campaigns:write" permission may create campaigns
    e.POST("/api/campaigns", createCampaignHandler,
        auth.Perm, "campaigns:write")
}

```

## Transport and Deployment Security

### HTTPS Requirements

The ListMonk source code **does not enforce TLS** internally. The application trusts the deployment environment—whether a reverse proxy, Docker container, or Kubernetes ingress—to terminate TLS before traffic reaches the Go server. Always configure HTTPS at the reverse proxy layer (nginx, Traefik, or cloud load balancers) when exposing the admin API.

### Rate Limiting

ListMonk does **not** implement built-in rate limiting for the admin API. To protect against brute-force credential guessing or denial-of-service attacks, deploy an external rate limiter such as nginx's `limit_req` module, HAProxy速率限制, or cloud-provider WAF rules in front of the application.

## Secret Handling and Cryptographic Practices

### Bcrypt Hash Storage

API keys are stored in the `users` table using **bcrypt hashing** (field `users.password`). The system never returns plain-text keys through any endpoint, preventing accidental exposure via logs, backups, or database dumps. When rotating keys, generate new credentials through the UI or CLI ([`cmd/admin.go`](https://github.com/knadh/listmonk/blob/main/cmd/admin.go) helper), then delete the old entries.

### Timing Attack Prevention

The authentication layer explicitly uses `subtle.ConstantTimeCompare` from Go's crypto/subtle package when validating tokens. This ensures the comparison operation takes constant time regardless of how many characters match, eliminating timing side-channels that could leak valid credential prefixes.

## CSRF and Request Forgery Protection

**Cross-Site Request Forgery (CSRF)** attacks are mitigated through the API's design. Because the admin API requires an explicit token in the `Authorization` header rather than relying solely on browser cookies, typical CSRF vectors (malicious forms exploiting automatic cookie transmission) are ineffective. The session cookie authentication path is reserved for the web UI, which operates under the same-origin policy protections.

## Auditing and Monitoring

Authentication failures and session validation errors generate log statements via `log.Printf` calls in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go). These logs—including messages like "invalid API credentials" and "error creating login session"—provide operators with an audit trail to detect suspicious scanning activity or unauthorized access attempts.

## Summary

- **Token-based authentication** uses bcrypt hashing and constant-time comparison in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go) to prevent timing attacks.
- **Role-based authorization** via the `Perm` middleware enforces granular permissions, with super-admins (identified by `SuperAdminRoleID`) bypassing all checks.
- **No built-in rate limiting** requires external protection (nginx, cloud WAF) for production deployments.
- **TLS termination** must occur at the reverse proxy level, as ListMonk does not enforce HTTPS internally.
- **CSRF protection** is inherent to the token-based design, while session cookies are validated through the same secure middleware.

## Frequently Asked Questions

### How are API keys stored in ListMonk?

API keys are stored as **bcrypt hashes** in the `users` table within the `password` field. The application never stores or transmits plain-text API keys, ensuring that database breaches or backup exposures do not compromise credential material. Only the bcrypt hash is retained for comparison during the authentication process.

### Does ListMonk protect against timing attacks on API authentication?

Yes. The authentication middleware in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go) uses **`subtle.ConstantTimeCompare`** when validating API tokens against stored hashes. This cryptographic function ensures the comparison operation executes in constant time regardless of where the strings differ, preventing attackers from inferring valid credentials through response-time analysis.

### Can I restrict API access to specific permissions rather than granting full admin rights?

Absolutely. ListMonk implements **role-based access control** through the `PermissionsMap` system. When creating API users through the admin interface or CLI ([`cmd/admin.go`](https://github.com/knadh/listmonk/blob/main/cmd/admin.go)), you can assign specific permissions (e.g., `campaigns:read`, `subscribers:write`) rather than super-admin privileges. The `Perm` middleware validates these permissions on each request, rejecting unauthorized operations while allowing super-admins (role ID `SuperAdminRoleID`) unrestricted access.

### Is rate limiting built into the ListMonk admin API?

No, ListMonk does not include built-in rate limiting for the admin API. To protect against brute-force attacks or API abuse, you must implement rate limiting at the infrastructure layer using a reverse proxy like nginx (`limit_req` module), HAProxy, or a cloud Web Application Firewall (WAF) in front of your ListMonk instance.