Authentication
What does it do?
Authentication controls who may access a route. GateControl offers three tiers:
No authentication:
Client → Caddy → Backend ✓ (anyone has access)
Basic Auth:
Client → Caddy → "Username/password?" (browser dialog) → Backend ✓
Route Auth:
Client → Caddy → Login page (Email + password + 2FA) → Session cookie → Backend ✓
No authentication
The route is publicly accessible. Anyone with the URL can access it.
When useful:
- Public websites and APIs
- In combination with Peer ACL (VPN-only access, no login required)
- Services that bring their own authentication (e.g. Nextcloud, Gitea)
Caution: Without auth and without ACL, the route is visible to the entire internet. The backend should then have its own login function.
Basic Auth
HTTP Basic Authentication — the browser shows a native login dialog.
How it works internally
Caddy inserts an authentication handler before all other handlers:
{
"handler": "authentication",
"providers": {
"http_basic": {
"accounts": [{
"username": "admin",
"password": "$2a$14$..."
}]
}
}
}
Flow:
- Client opens the route
- Caddy responds with
401 UnauthorizedandWWW-Authenticate: Basic - Browser shows native login dialog
- Client sends
Authorization: Basic <base64(user:pass)>header - Caddy checks the username against the stored value and the password against the bcrypt hash
- On success: the request is forwarded to the backend
- The
Authorizationheader is sent with every request (no session management)
Properties
| Property | Value |
|---|---|
| Password storage | bcrypt hash (admin login: argon2id) |
| Session management | None — credentials on every request |
| Logout | Not possible (browser caches credentials) |
| 2FA | Not possible |
| Browser compatibility | All browsers |
| API compatibility | All HTTP clients (curl -u user:pass) |
| Accounts per route | 1 |
Use cases
Protecting developer tools: Route phpmyadmin.example.com → phpMyAdmin. Basic Auth with a strong password. Simple, no setup, works with any browser and tool.
Securing API access: curl -u admin:secret https://api.example.com/data — Basic Auth is ideal for APIs since no cookie/session management is required.
Route Auth
Custom login page with multiple authentication methods and optional two-factor authentication.
How it works internally
Route Auth uses Caddy's forward_auth mechanism:
- Caddy forwards
/route-auth/*paths directly to GateControl (port 3000) (login page, assets) - For all other paths: forward-auth subrequest to
GET /route-auth/verify - GateControl checks the session cookie
- On a valid session (2xx): the request is forwarded to the backend
- On an invalid session:
302 Redirectto the login page
{
"handler": "reverse_proxy",
"upstreams": [{ "dial": "127.0.0.1:3000" }],
"rewrite": { "method": "GET", "uri": "/route-auth/verify" },
"headers": {
"request": {
"set": {
"X-Route-Domain": ["app.example.com"],
"X-Forwarded-Method": ["{http.request.method}"],
"X-Forwarded-Uri": ["{http.request.uri}"]
}
}
}
}
Three login methods
Email & Password
Classic login with email address and password.
- Password is stored as a bcrypt hash
- No external service required
- Simplest method
Email & Code
A 6-digit one-time code is sent by email.
- Requires configured SMTP (Settings → Email)
- Code is time-limited
- No password required — only access to the email
- Ideal for users who don't want to remember passwords
TOTP (Time-based One-Time Password)
Authenticator app generates 6-digit codes.
- Compatible with: Google Authenticator, Microsoft Authenticator, Authy, 1Password, etc.
- No password, no email access required
- Requires one-time QR-code setup
Two-factor authentication (2FA)
Route Auth supports optional 2FA. Email & Password is used as the first factor combined with:
- Email Code as the second factor: after the password, a 6-digit code is sent by email
- TOTP as the second factor: after the password, a TOTP code from the authenticator app is required
Session management
| Property | Value |
|---|---|
| Session duration | Configurable: 1h, 12h, 24h, 7d, 30d |
| Session storage | Cookie |
| Logout | Yes (destroys the session) |
| Multiple devices | Yes (separate sessions) |
Custom branding
Each route can have its own login page:
- Logo (upload or URL)
- Title and description text
- Colors (primary, background)
- Background image
TOTP setup (step by step)
- Create/edit route → Auth Type: Route Auth
- Select Method: TOTP (or as second factor for 2FA)
- Save the route
- Open the route again in the edit modal
- A QR code appears in the TOTP section
- Scan the QR code with an authenticator app
- Enter the 6-digit confirmation code
- TOTP is active
Important: The QR code is only displayed once the route has been saved. When creating a new route, the edit modal must be reopened after the first save.
Comparison table
| Property | No Auth | Basic Auth | Route Auth |
|---|---|---|---|
| Security level | None | Medium | High |
| Login UI | None | Browser dialog | Custom login page |
| Password storage | — | bcrypt | bcrypt |
| 2FA possible | No | No | Yes |
| Session/Logout | — | No/No | Yes/Yes |
| API-compatible | Yes | Yes (curl -u) |
No (cookie-based) |
| Custom branding | — | No | Yes |
| Accounts per route | — | 1 | 1 |
| Email required | No | No | Yes (for email methods) |
| SMTP required | No | No | Only for Email Code |
| Works with L4 | — | No | No |
Combination with other features
| Combination | Effect |
|---|---|
| Auth + ACL | First the VPN-IP check, then login |
| Auth + Force HTTPS | Mandatory with Basic Auth (otherwise credentials in plaintext!) |
| Auth + Rate Limiting | Basic Auth: rate limit before auth. Route Auth: rate limit after auth |
| Auth + IP filter | Route Auth + IP filter: IP is checked in forward-auth. Basic Auth + IP filter: cannot be combined |
Important notes
- Basic Auth and Route Auth are mutually exclusive. A route can only use one of the two methods.
- Basic Auth without HTTPS is insecure. The password is base64-encoded (not encrypted) in the
Authorizationheader. Force HTTPS must be active. - Route Auth is not API-compatible. It is based on session cookies and a login page. For API access use Basic Auth.
- Authentication is only available for HTTP routes, not for L4 (TCP/UDP).
- With Route Auth: the forward-auth subrequest runs on every request (session cookie check). This adds minimal latency (~1-5ms).
- Basic Auth has no built-in brute-force protection. Combine it with Rate Limiting for additional security.
- TOTP secrets are stored in the database. A database backup also secures the TOTP configuration.
Admin login
Independent of Route Auth, GateControl protects its own Admin UI (port 3000 behind Caddy) via POST /login:
- The password hash in
users.password_hashis argon2id. Older bcrypt hashes are migrated on the next login. - Sessions are server-side (
express-session); on a successful login the session ID is regenerated (session fixation protection). - After too many failed attempts a lockout kicks in: 5 attempts / 15 minutes per username (configurable under Settings → Security,
security.lockout.max_attempts/security.lockout.duration). - Lockouts are stored in
login_attempts; after the lockout expires the account is automatically unlocked. An admin can manually unlock a locked account (lockout.unlockAccount).
Details on response codes and payloads are in the canonical API.md (section Auth).
API tokens and machine fingerprint
API tokens (gc_…) are scoped (see API.md → Token Scopes). Relevant properties:
- Stored as SHA-256 hash; the plaintext token is only shown once at creation.
scopesis required (e.g.read-only,full-access,routes,client,gateway).- Optional:
expires_at, binding touser_idorpeer_id, and machine fingerprint binding: on first use the SHA-256 fingerprint of the calling machine is stored; later calls must send the same fingerprint or are rejected. Reset only via the Admin UI. - Route Auth uses the same lockout table (
type = 'route_auth').