1 Password Protection
Ric Harvey edited this page 2025-12-01 20:56:08 +00:00

Password Protection

Protect your Forgejo Pages sites with secure password authentication.

Overview

The password protection feature allows you to require authentication before users can access your site. It uses:

  • SHA256 password hashing - Passwords never stored in plaintext
  • HMAC-signed cookies - Prevents cookie tampering
  • Secure cookies - HttpOnly, Secure (HTTPS), SameSite=Strict
  • Beautiful login UI - Gradient design with centered form

Quick Setup

Step 1: Generate Password Hash

echo -n "your-password-here" | shasum -a 256

Example output:

5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

Step 2: Add to .pages File

enabled: true
password: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

Step 3: Configure Traefik (Optional)

http:
  middlewares:
    pages-server:
      plugin:
        pages-server:
          pagesDomain: pages.example.com
          forgejoHost: https://git.example.com
          authCookieDuration: 3600  # 1 hour
          authSecretKey: "randomly-generated-secret-key"

Step 4: Commit and Push

git add .pages
git commit -m "Add password protection"
git push

Advanced Configuration

Control how long users stay logged in:

authCookieDuration: 14400  # 4 hours in seconds

Recommendations:

  • Public sites: 1-4 hours (3600-14400)
  • Private sites: 8-24 hours (28800-86400)
  • Internal sites: 7 days (604800)

For maximum security, always configure authSecretKey:

# Generate random secret key
openssl rand -base64 32
authSecretKey: "your-random-secret-here"

Without authSecretKey, cookies are still functional but not cryptographically signed.

Password Generation Methods

Using shasum (Mac/Linux)

echo -n "mypassword" | shasum -a 256

Using OpenSSL

# Generate random password
openssl rand -base64 16

# Hash a password
echo -n "your-password" | openssl dgst -sha256

Using Python

import hashlib
password = "mypassword"
hash_object = hashlib.sha256(password.encode())
print(hash_object.hexdigest())

Using Node.js

const crypto = require('crypto');
const password = 'mypassword';
const hash = crypto.createHash('sha256').update(password).digest('hex');
console.log(hash);

Login Page

When users visit a password-protected site, they see a beautiful login page:

  • 🎨 Purple gradient background
  • 📱 Responsive design
  • 🔍 Repository name displayed
  • ⚠️ Error messages for wrong passwords
  • ⌨️ Auto-focus on password field
  • 🔒 Secure HTTPS-only form submission

How It Works

  1. User visits protected site
  2. Plugin checks for authentication cookie
  3. No cookie found → Show login page
  4. User enters password
  5. Password hashed with SHA256
  6. Hash compared with stored hash
  7. Match → Set signed cookie, redirect to site
  8. No match → Show error message

Security Features

Password Hashing

  • Passwords stored as SHA256 hashes in .pages file
  • Original password never stored
  • One-way hashing prevents password recovery
  • HttpOnly: JavaScript cannot access cookies (XSS protection)
  • Secure: Only sent over HTTPS connections
  • SameSite=Strict: Prevents CSRF attacks
  • HMAC Signature: Prevents cookie tampering (when authSecretKey configured)

Cache TTL

  • Password hashes cached for 60 seconds
  • Reduces .pages file reads
  • Automatic cache refresh every minute
  • No password stored in cache (only hash)

Multiple Protected Sites

Each repository gets its own authentication:

Cookie: pages_auth_username_repo1  # For repo1
Cookie: pages_auth_username_repo2  # For repo2

Users must authenticate separately for each protected repository.

Removing Protection

Simply remove the password: line from .pages:

enabled: true
# password: removed

Wait up to 60 seconds for cache to expire, or clear cache manually.

Troubleshooting

Login page doesn't appear

Problem: Site loads normally without asking for password

Solutions:

  1. Verify password: field exists in .pages file
  2. Check hash is valid SHA256 (64 hex characters)
  3. Wait 60 seconds for cache to update
  4. Clear password cache:
    redis-cli DEL "password:username:repository"
    

Wrong password error even with correct password

Problem: "Incorrect password" message with right password

Solutions:

  1. Verify you're using the hash in .pages, not plaintext
  2. Check for extra spaces/newlines in hash
  3. Re-generate hash: echo -n "password" | shasum -a 256
  4. Ensure exact password match (case-sensitive)

Problem: Have to login on every page

Solutions:

  1. Ensure site served over HTTPS (cookies are Secure-only)
  2. Check browser allows cookies
  3. Verify authCookieDuration is positive number
  4. Check browser cookie settings

Getting redirected after login

Problem: Stuck in login/redirect loop

Solutions:

  1. Clear browser cookies for the site
  2. Check authSecretKey hasn't changed
  3. Verify system clock is accurate
  4. Check browser JavaScript is enabled

Best Practices

  1. Use strong passwords - 16+ characters, mixed case, numbers, symbols
  2. Configure authSecretKey - Enables HMAC cookie signing
  3. Set appropriate cookie duration - Balance security vs. convenience
  4. Use HTTPS - Required for secure cookies
  5. Rotate passwords - Change periodically for sensitive sites
  6. Monitor access - Check logs for failed login attempts

Example Configurations

Basic Protection

enabled: true
password: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

With Custom Domain

enabled: true
custom_domain: private.example.com
password: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

High Security (Short Duration)

Traefik config:

authCookieDuration: 1800  # 30 minutes
authSecretKey: "long-random-secret-key"

.pages:

enabled: true
password: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

Low Security (Long Duration)

Traefik config:

authCookieDuration: 604800  # 7 days
authSecretKey: "random-secret"

.pages:

enabled: true
password: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8