# Access control

> Source: https://ferron.sh/docs/use-cases/security/access-control

Ferron supports several access control patterns. These range from simple IP-based `block`/`allow` rules to authenticated areas with HTTP Basic Authentication or an external authentication backend.

> [!tip]
> Test restrictive rules with a temporary endpoint first to avoid locking yourself out. Prefer `location` matches when possible. Use conditional matchers only when you need pattern matching.

## Restrict a path by client IP (block/allow)

Use `block` and `allow` directives to control access by IP or CIDR range:

```ferron
# Replace "example.com" with your domain name.
example.com {
    location / {
        root /var/www/html
    }

    # Only allow these networks to access /admin; everyone else gets 403.
    location /admin {
        allow "203.0.113.0/24" "2001:db8:1234::/48"
        root /var/www/admin
    }
}
```

When you configure `allow`, only the listed IPs/CIDRs receive permission. All others receive a 403 Forbidden response.

> [!important]
> If Ferron is behind a reverse proxy/load balancer, configure `client_ip_from_header`. This makes sure IP-based rules use the client IP instead of the proxy IP. See [HTTP host directives](https://ferron.sh/docs/configuration/server/host.md).

### Combined block and allow

When you configure both `block` and `allow`:

1. If the IP matches an `allow` entry and a `block` entry → blocked (block takes precedence)
2. If the IP matches only an `allow` entry → allowed
3. If the IP matches only a `block` entry → blocked
4. If the IP matches neither → allowed (unless the allow list is non-empty)

```ferron
example.com {
    root /var/www/html

    allow "192.168.1.0/24"
    block "192.168.1.100"
}
```

In this example: `192.168.1.50` → allowed, `192.168.1.100` → blocked, `10.0.0.1` → denied.

### Block sensitive paths globally

Use a global block to deny access across all hosts:

```ferron
* {
    # Block known abusive addresses globally.
    block "198.51.100.10" "203.0.113.0/24"
}
```

## Protect an area with Basic Auth

Use the `basic_auth` directive to require HTTP Basic Authentication:

```ferron
# Replace "example.com" with your domain name.
example.com {
    location / {
        root /var/www/html
    }

    location /admin {
        basic_auth {
            realm "Admin Area"
            users {
                admin "$argon2id$v=19$m=19456,t=2,p=1$..."
            }
        }

        root /var/www/admin
    }
}
```

Ferron supports only hashed passwords. Ferron accepts the following hash formats:

| Prefix                 | Algorithm              |
| ---------------------- | ---------------------- |
| `$argon2id$`           | Argon2id (recommended) |
| `$argon2i$`            | Argon2i                |
| `$argon2d$`            | Argon2d                |
| `$pbkdf2$`             | PBKDF2                 |
| `$pbkdf2-sha256$`      | PBKDF2-SHA256          |
| `$scrypt$`             | scrypt                 |
| `$2a$`, `$2b$`, `$2y$` | bcrypt                 |

> [!important]
> For Basic Auth, always use TLS. The `Authorization` header sends credentials on every request.

### Brute-force protection

Brute-force protection is **enabled by default**:

```ferron
example.com {
    location /admin {
        basic_auth {
            realm "Admin Area"
            users {
                admin "$argon2id$v=19$m=19456,t=2,p=1$..."
            }

            brute_force_protection {
                max_attempts 5
                lockout_duration "15m"
                window "5m"
            }
        }
    }
}
```

## Conditional access control

Use named matchers with `if`/`if_not` for more complex access control logic:

```ferron
match internal_network {
    request.header.x_forwarded_for ~ r"^10\.0\.0\."
}

example.com {
    location /admin {
        if_not internal_network {
            abort
        }

        root /var/www/admin
    }
}
```

This aborts the connection for any request that does not come from the `10.0.0.0/8` network.

## Deny access to sensitive files

Use conditional matching to block access to dotfiles and sensitive paths:

```ferron
match sensitive_path {
    request.uri.path ~ r"^/(?:\.|config|private|backup)"
}

example.com {
    root /var/www/html

    if sensitive_path {
        status 403 {
            body "Access denied"
        }
    }
}
```

## Forwarded authentication

For external authentication, use the `auth_to` directive to forward each request to an authentication backend:

```ferron
example.com {
    auth_to http://auth.example.com/auth

    root /var/www/html
}
```

When the backend returns a success status (2xx), the request continues. On failure (4xx/5xx), Ferron returns the response of the backend to the client. You can copy headers from the auth response to the original request for downstream use:

```ferron
api.example.com {
    auth_to http://auth.example.com/validate {
        copy X-Auth-User X-Auth-Roles
    }

    proxy http://backend:8080
}
```

> [!info]
> For full configuration details, see [Forwarded authentication](https://ferron.sh/docs/configuration/security/forwarded-auth.md).