How to Create an Authentication Portal for Your Domains with Caddy
How to Create an Authentication Portal for Your Domains with Caddy
Caddy is a modern web server that automatically provides HTTPS via Let’s Encrypt. However, the standard Caddy build does not include the caddy-security
module, which is required to create an authentication portal and manage access to your domains. In this article, I’ll walk you through building Caddy with this module using xcaddy
, configuring an authentication portal, and securing your web applications.
Prerequisites
Before starting, ensure you have:
- A server with an operating system (e.g., Ubuntu 22.04).
- Command-line access with superuser privileges (
sudo
). - Basic knowledge of web servers and configuration files.
- A domain or subdomain (e.g.,
example.com
) with DNS records pointing to your server. - Go installed (version 1.16 or higher) for building Caddy.
Step 1: Building Caddy with the caddy-security
Module
Since the caddy-security
module isn’t included in the standard Caddy build, we’ll compile a custom version using xcaddy
.
1.1. Installing xcaddy
xcaddy
is a tool for building Caddy with additional modules. Install it as follows:
sudo apt update
sudo apt install -y golang
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
After running these commands, xcaddy
will be available in ~/go/bin/xcaddy
. Ensure ~/go/bin
is in your $PATH
for global access:
export PATH=$PATH:~/go/bin
1.2. Building Caddy with caddy-security
Now, build Caddy with the caddy-security
module:
~/go/bin/xcaddy build --with github.com/greenpau/caddy-security
This command creates an executable caddy
file in the current directory.
1.3. Installing the Custom Caddy Build
Move the compiled file to a system directory for global access:
sudo mv caddy /usr/bin/
Verify the version to ensure the module is included:
caddy version
The output should show a custom build version of Caddy.
Step 2: Setting Up User Storage
The authentication portal in Caddy uses a JSON file to store user data. We’ll create a users.json
file using a script.
2.1. Creating a Data Directory
Create a directory for storing data and set appropriate permissions:
sudo mkdir -p /data/.local/caddy
sudo chown caddy:caddy /data/.local/caddy
2.2. Generating a User with a Script
Use this script to simplify user creation. It prompts for user details and generates users.json
.
Save this code as generate_users_json.sh
:
#!/bin/bash
# Check dependencies
if ! command -v python3 &> /dev/null; then
echo "Error: Python3 is not installed. Install it with: 'sudo apt install python3'."
exit 1
fi
if ! python3 -c "import bcrypt" &> /dev/null; then
echo "Error: Python bcrypt module is not installed. Install it with: 'pip3 install bcrypt'."
exit 1
fi
if ! command -v uuidgen &> /dev/null; then
echo "Error: uuidgen is not installed. Install it with: 'sudo apt install uuid-runtime'."
exit 1
fi
# Prompt for input
read -p "Enter username: " USERNAME
read -p "Enter email: " EMAIL
read -s -p "Enter password: " PASSWORD
echo
# Validate input
if [[ -z "$USERNAME" || -z "$EMAIL" || -z "$PASSWORD" ]]; then
echo "Error: All fields must be filled."
exit 1
fi
if [[ ${#USERNAME} -lt 3 || ${#USERNAME} -gt 50 || "$USERNAME" =~ [^a-z] ]]; then
echo "Error: Username must be 3-50 characters, lowercase letters (a-z) only."
exit 1
fi
if [[ ${#PASSWORD} -lt 8 ]]; then
echo "Error: Password must be at least 8 characters."
exit 1
fi
# Extract domain
DOMAIN=$(echo "$EMAIL" | cut -d'@' -f2)
if [[ -z "$DOMAIN" ]]; then
echo "Error: Invalid email format."
exit 1
fi
# Generate values
UUID=$(uuidgen)
DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
HASH=$(python3 -c "import bcrypt; print(bcrypt.hashpw('$PASSWORD'.encode(), bcrypt.gensalt(10)).decode())")
# Create users.json
cat <<EOF > /data/.local/caddy/users.json
{
"version": "1.1.7",
"policy": {
"password": {
"min_length": 8,
"max_length": 128
},
"user": {
"min_length": 3,
"max_length": 50,
"allow_non_alpha_numeric": false,
"allow_uppercase": false
}
},
"revision": 1,
"last_modified": "$DATE",
"users": [
{
"id": "$UUID",
"username": "$USERNAME",
"email_address": {
"address": "$EMAIL",
"domain": "$DOMAIN"
},
"email_addresses": [
{
"address": "$EMAIL",
"domain": "$DOMAIN"
}
],
"passwords": [
{
"purpose": "generic",
"algorithm": "bcrypt",
"hash": "$HASH",
"cost": 10,
"expired_at": "0001-01-01T00:00:00Z",
"created_at": "$DATE",
"disabled_at": "0001-01-01T00:00:00Z"
}
],
"created": "$DATE",
"last_modified": "$DATE",
"roles": [
{
"name": "admin",
"organization": "authp"
}
]
}
]
}
EOF
# Set permissions
sudo chown caddy:caddy /data/.local/caddy/users.json
sudo chmod 640 /data/.local/caddy/users.json
echo "File /data/.local/caddy/users.json created successfully!"
echo "Restart Caddy: sudo systemctl restart caddy"
How to Use the Script:
- Save it as
generate_users_json.sh
. - Make it executable:
chmod +x generate_users_json.sh
- Run it:
./generate_users_json.sh
- Enter details:
- Username (e.g.,
john
). - Email (e.g.,
[email protected]
). - Password (minimum 8 characters).
- Username (e.g.,
The script creates /data/.local/caddy/users.json
with a user assigned the authp/admin
role.
Step 3: Configuring Caddy for Authentication
Now, configure the Caddyfile to enable the authentication portal.
3.1. Base Caddy Configuration
Open /etc/caddy/Caddyfile
:
sudo nano /etc/caddy/Caddyfile
Add global settings at the top:
{
storage file_system {
root /var/lib/caddy
}
email [email protected]
order authenticate before respond
order authorize before respond
security {
local identity store localdb {
realm local
path /data/.local/caddy/users.json
}
authentication portal my_portal {
crypto default token lifetime 3600
enable identity store localdb
cookie domain .example.com
ui {
links {
"Dashboard" "/dashboard" icon "las la-tachometer-alt"
"My Identity" "/auth/whoami" icon "las la-user"
}
}
transform user {
match origin local
action add role authp/admin
}
}
authorization policy my_policy {
set auth url /auth
allow roles authp/admin
acl rule {
comment "Allow authenticated admins"
match role authp/admin
allow stop log info
}
acl rule {
comment "Deny all others"
match any
deny log warn
}
}
}
}
- Replace
[email protected]
with your email. - Replace
.example.com
with your domain (e.g.,.mydomain.com
).
3.2. Domain Configuration
Add a block for your domain:
sub.example.com {
route {
handle /auth {
rewrite * /auth
request_header +X-Forwarded-Prefix /auth
authenticate with my_portal
}
route /auth* {
authenticate with my_portal
reverse_proxy 127.0.0.1:8080 {
header_up X-Real-IP {remote}
header_up Host {host}
}
}
route /* {
authorize with my_policy
reverse_proxy 127.0.0.1:8080 {
header_up X-Real-IP {remote}
header_up Host {host}
}
}
}
log {
output file /var/log/caddy/sub.example.com-access.log {
roll_size 30mb
roll_keep 10
roll_keep_for 720h
}
level ERROR
}
}
- Replace
sub.example.com
with your domain. - Adjust
reverse_proxy
to match your backend (address and port).
Step 4: Validating and Restarting Caddy
4.1. Validate Configuration
Check the Caddyfile for errors:
caddy validate --config /etc/caddy/Caddyfile
If you see Valid configuration
, proceed.
4.2. Restart Caddy
Apply changes:
sudo systemctl restart caddy
Verify the status:
systemctl status caddy
The service should be active (running)
.
Step 5: Testing the Authentication Portal
- Open a browser and go to
https://sub.example.com/auth
. - Enter the username and password from
users.json
. - After logging in, verify access to protected resources.
If access fails, check:
- Data in
users.json
. - Role consistency in the configuration.
Additional Tips
Multiple Domains
For different domains, create separate portals:
authentication portal another_portal {
cookie domain .anotherdomain.com
...
}
Logging
Enable debug logs:
{
log {
output file /var/log/caddy/caddy.log
level DEBUG
}
}
Security
- Keep Caddy updated.
- Use strong passwords.
- Configure a firewall (e.g.,
ufw
) to allow only ports 80 and 443.
Conclusion
You now have a fully configured authentication portal with Caddy. This setup is scalable—add users, domains, and policies as needed. For further assistance, refer to the Caddy documentation or ask in the community!
- Our community Openode.XYZ OpeNode.xyz
- Aeza VPS (+15% on payment) Aeza.net
- Best EU hosting (+1 month free) Kamatera.com
- VPS hosting - 4vps.su (-10% discount!) 4VPS.su
- TG Channel TG-Channel Neonode.cc