Basic Auth
| Block name | Context | Label |
|---|---|---|
basic_auth | Definitions Block | required |
The basic_auth block lets you configure HTTP basic auth for your gateway. Like all
access control types, the basic_auth block is defined in the
definitions block and can be referenced in all configuration
blocks by its required label.
Basic Auth is intended for simple access control situations where a static list of users is sufficient. This could be to protect a staging environment, or to expose a dedicated API to a single internal client, such as a neighboring microservice.
The user is accessible via request.context.<label>.user variable for successfully authenticated requests.
If both user/password and htpasswd_file are configured, the incoming
credentials from the Authorization request HTTP header field are checked against
user/password if the user matches, and against the data in the file referenced
by htpasswd_file otherwise.
Example
Using inline credentials
server {
api {
endpoint "/private" {
access_control = ["myauth"]
proxy {
backend = "my_backend"
}
}
}
}
definitions {
basic_auth "myauth" {
user = "john"
password = "s3cr3t"
}
}
Using an htpasswd file
definitions {
basic_auth "myauth" {
htpasswd_file = "htpasswd"
}
}
The htpasswd file uses Apache’s htpasswd format:
john:$2y$05$/uonQYUtwm...
jane:$argon2id$v=19$m=65536,t=3,p=2$salt$hash
Attribute htpasswd_file
The file is loaded at startup and on configuration reload. When Couper is run with the -watch flag, changes to the htpasswd_file path are detected automatically and trigger a reload; otherwise restart Couper after changing it.
Couper supports the following password hash algorithms:
| Algorithm | htpasswd prefix | Recommended |
|---|---|---|
argon2id | $argon2id$ | yes |
argon2i | $argon2i$ | |
bcrypt | $2y$ | |
md5 | $apr1$ |
Choosing Argon2 parameters for security and performance
When generating your own password hashes, argon2id is the recommended choice as it provides a balanced approach to resisting both side-channel and GPU-based attacks (see OWASP Password Storage Cheat Sheet).
The argon2 hash encodes the parameters used to derive it: m (memory in KiB), t (iterations) and p (parallelism). Couper re-runs the key derivation with these parameters on every authenticated request, so the choice has both security and runtime cost implications.
OWASP currently recommends for argon2id: m=19456 (≈19 MiB), t=2, p=1.
Memory cost is per request. Couper allocates m KiB on every basic auth verification, which is why parameter choice matters for the gateway’s resident memory under load. Couper treats twice the highest OWASP-recommended values as a recommended maximum:
| Parameter | Recommended max | OWASP highest |
|---|---|---|
m (KiB) | 94208 | 47104 |
t | 10 | 5 |
p | 2 | 1 |
Entries above these still load — so upgrading Couper cannot break a deployment whose htpasswd file predates this guidance — but Couper logs a startup warning naming the offending line. Lower the parameter to bound per-request cost, or pair the access control with a rate limiter (see below). Entries that could never authenticate (t or p below 1, or a malformed hash) are still rejected at startup.
Couper limits the derivations that run at the same time to a memory budget. The budget applies to the process, and thus to all basic_auth blocks together. It is 256MiB by default, set with beta_argon2_memory_budget in the settings block. Couper divides the budget by the m of the most expensive loaded entry and runs that many derivations in parallel, at most one per core, because one derivation keeps one core busy. The peak memory then follows the budget, and not the number of requests. Example: with m=19456 and four cores, four derivations run at a time and use 76 MiB. With m=94208, two run and use 184 MiB. Couper logs the effective limit at startup. If one derivation alone exceeds the budget, Couper runs one at a time and logs a warning. A request that ends while it waits for a slot leaves the queue, and Couper runs no derivation for it. Each attempt still costs what the parameters in the hash specify. A caller that sends many different passwords therefore pays a full derivation each time — see “Pair with a rate limiter” below.
Pair with a rate limiter
Argon2 is expensive by design. The limit above bounds the peak memory, but an attacker who sends many different passwords still causes one full derivation per attempt. These derivations also fill the queue before the requests of legitimate callers. Put a beta_rate_limiter access control before the basic auth in the access_control list of the endpoint. Couper then rejects an abusive caller before it starts argon2 work:
server {
api {
endpoint "/private" {
access_control = ["ip_rate", "myauth"]
proxy {
backend = "my_backend"
}
}
}
}
definitions {
beta_rate_limiter "ip_rate" {
period = "60s"
per_period = 10
period_window = "sliding"
key = request.remote_ip
}
basic_auth "myauth" {
htpasswd_file = "htpasswd"
}
}
Access controls run in the order listed: the rate limiter rejects the request first, so basic auth is invoked only for callers within the budget.
Order matters across levels, too. Access controls attached at the server or api level run before those on the endpoint. If basic auth is attached at an outer level and the rate limiter only on the endpoint, the argon2 derivation runs before the limiter can reject — defeating the protection. Keep the rate limiter ahead of basic auth in the effective order: list it first in the same access_control list (as above), or attach it at the same or an outer level.
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
custom_log_fields | object | - | Log fields for custom logging. Inherited by nested blocks. |
htpasswd_file | string | - | The htpasswd file. |
password | string | - | The corresponding password. |
realm | string | - | The realm to be sent in a WWW-Authenticate response HTTP header field. |
user | string | - | The user name. |
Nested Blocks
| Name | Description |
|---|---|
error_handler | Configures an error handler (zero or more). |