Skip to main content

Authentication Setup

Step-by-step guide to setting up authentication in EnvCat.

Overview

EnvCat uses session-based authentication to protect bundle management and administrative functions. The authentication system:

  • Protects: Bundle management, bundle editing, user settings
  • Keeps public: CLI approval flow (/approve/[id])
  • Uses: Argon2id password hashing, Redis session storage
  • Provides: Rate limiting, account lockout, security headers

Key security features:

  • Industry-standard Argon2id password hashing (OWASP #1 recommendation)
  • HTTP-only session cookies (XSS protection)
  • SameSite=Lax cookies (CSRF protection)
  • Rate limiting (5 login attempts per 15 minutes)
  • Account lockout (10 failed attempts → 1 hour lock)

Why Local-First Auth is Secure

Unlike cloud-based secrets managers, your credentials in EnvCat:

  1. Never leave your machine - No external authentication service
  2. Can't be breached remotely - No cloud provider to hack
  3. No third-party risk - No dependency on external services
  4. Air-gapped capable - Works fully offline
  5. Complete control - You own the data and the infrastructure

Trade-offs:

  • Password reset requires manual intervention (no email-based reset)
  • Sessions don't sync across machines (local Redis storage)
  • You're responsible for backups (SQLite database)

For local-first deployment, these trade-offs are acceptable and actually increase security.


First-Time Setup

Prerequisites

Ensure services are running:

docker compose up -d

Verify services are healthy:

docker compose ps

You should see:

  • api - Running on port 8080 (internal)
  • web - Running on port 8888 (public)
  • redis - Running on port 6379 (internal)

Step 1: Navigate to Web UI

Open your browser and navigate to:

http://localhost:8888

What happens:

  • Web UI checks if any users exist (calls /api/v1/auth/setup-required)
  • If no users exist, you're redirected to /setup
  • If users exist, you're redirected to /login

Step 2: Create Admin Account

On the setup page, you'll see a form to create your admin account.

Form fields:

  • Username: 3-32 characters, lowercase, alphanumeric + underscore/hyphen
  • Password: Minimum 10 characters (no complexity requirements)
  • Confirm Password: Must match password

Screenshot placeholder: Setup page with form fields

Example credentials:

Username: admin
Password: my-secure-password-123

Password recommendations:

  • Use a passphrase: correct-horse-battery-staple
  • Longer is better than complex: thisisaverylongpassword > P@ssw0rd!
  • Unique to EnvCat (don't reuse from other services)
  • Store in password manager for safety

Click "Create Account".

Step 3: Automatic Login

After account creation:

  1. User is automatically logged in (session created in Redis)
  2. Session cookie is set in your browser (constants_session)
  3. You're redirected to /login (where you're already authenticated)
  4. Then immediately redirected to /bundles (main dashboard)

What just happened:

  • Password was hashed using Argon2id (takes ~200-300ms)
  • User record created in SQLite database
  • Session created in Redis with 24-hour TTL
  • HTTP-only, SameSite=Lax cookie sent to browser

Step 4: Verify Authentication

On the bundles page, you should see:

  • Your username in the header (e.g., "Logged in as: admin")
  • Logout button in the header
  • Bundle management interface
  • No login prompt

Screenshot placeholder: Authenticated bundles dashboard with username visible

Try accessing a protected route directly:

http://localhost:8888/bundles

You should see the bundles page (not redirected to login).

Try accessing the setup page again:

http://localhost:8888/setup

You should be redirected to /login (setup is no longer available).


Logging In (Existing User)

If you've already created an account and need to log in again:

Step 1: Navigate to Login Page

Open your browser and navigate to:

http://localhost:8888/login

Or, if you're logged out and try to access a protected route:

http://localhost:8888/bundles

You'll be redirected to:

http://localhost:8888/login?return=/bundles

The return parameter tells the system where to redirect you after successful login.

Step 2: Enter Credentials

Form fields:

  • Username: Your username (case-insensitive)
  • Password: Your password
  • Remember me: Optional checkbox

Screenshot placeholder: Login page with form fields and "Remember me" checkbox

"Remember me" behavior:

  • Unchecked (default): Session lasts 24 hours
  • Checked: Session lasts 30 days

When to use "Remember me":

  • Personal laptop: ✅ Safe to use
  • Shared machine: ❌ Don't use (logout when done)
  • Development server: ⚠️ Use with caution

Click "Sign In".

Step 3: Successful Login

After successful login:

  1. Session is created in Redis
  2. Session cookie is set in browser
  3. Failed login counter is reset to 0
  4. Last login timestamp is updated
  5. You're redirected to the return URL (or /bundles by default)

Session details:

  • Storage: Redis (key: constants_session:<session_id>)
  • TTL: 24 hours (default) or 30 days (remember me)
  • Renewal: Sliding window (TTL refreshes on each request)
  • Cookie name: constants_session
  • Cookie properties: HTTP-only, SameSite=Lax, Secure=false (local dev)

Managing Sessions

Session Lifetime

Default session (remember me = false):

  • Lifetime: 24 hours from last activity
  • Idle timeout: None (session stays active as long as TTL hasn't expired)
  • Renewal: Sliding window (each request refreshes the TTL)

Extended session (remember me = true):

  • Lifetime: 30 days from last activity
  • Idle timeout: None
  • Renewal: Sliding window

Example session timeline (default 24h):

12:00 PM - Login (TTL set to 12:00 PM + 24h = 12:00 PM tomorrow)
1:00 PM - Access /bundles (TTL refreshed to 1:00 PM + 24h)
2:00 PM - Access /bundles (TTL refreshed to 2:00 PM + 24h)
...
(24 hours of inactivity)
...
2:00 PM next day - Session expired, redirected to login

Checking Session Status

Your browser automatically checks session status on page load:

// Happens automatically on page load
GET /api/v1/auth/session

// Response (authenticated):
{
"authenticated": true,
"user": {
"id": "...",
"username": "admin"
}
}

If session is invalid or expired:

// Response (not authenticated):
{
"authenticated": false
}

// You're redirected to /login

Logging Out

To log out manually:

  1. Click the "Logout" button in the header
  2. Or navigate to: http://localhost:8888/logout

What happens:

  1. Session is deleted from Redis (cannot be reused)
  2. Session cookie is cleared from browser
  3. You're redirected to /login

After logout:

  • Trying to access protected routes redirects to /login
  • Session ID in cookie is no longer valid
  • Must log in again to access bundles

Session Persistence

Scenario: You close your browser and reopen it.

What happens:

  1. Browser sends session cookie automatically
  2. Web UI checks session validity (Redis lookup)
  3. If session is still valid (within TTL), you remain logged in
  4. If session expired, you're redirected to /login

Browser restart (same session):

1. Close browser tab
2. Reopen browser
3. Navigate to http://localhost:8888/bundles
4. Still logged in (session cookie persisted)

Docker restart (session lost):

1. docker compose down
2. docker compose up -d
3. Navigate to http://localhost:8888/bundles
4. Redirected to /login (Redis cleared, session lost)

Important: Redis is ephemeral. Sessions are lost when Redis restarts. This is intentional for local-first security.


Security Best Practices

Password Security

Choose a strong password:

  • ✅ Minimum 10 characters (longer is better)
  • ✅ Use passphrases: correct-horse-battery-staple
  • ✅ Mix of character types (but not required)
  • ✅ Unique to EnvCat (don't reuse)

Don't:

  • ❌ Use common passwords (password123, admin123)
  • ❌ Reuse passwords from other services
  • ❌ Share passwords with others
  • ❌ Store passwords in plaintext (use password manager)

Password strength examples:

Weak:     password123         (common, short)
Weak: P@ssw0rd! (common pattern)
Medium: my-secure-pass-456 (better, but predictable)
Strong: correct-horse-battery-staple (long passphrase)
Strong: thisisaverylongpasswordthatishardtoguess (very long)

Why length > complexity:

  • thisisaverylongpassword = 24 characters = ~10^44 combinations
  • P@ssw0rd1 = 10 characters = ~10^17 combinations (10 million times weaker)

Session Security

On personal machines:

  • ✅ Use "Remember me" for convenience (30-day sessions)
  • ✅ Logout when done (if machine is shared later)
  • ✅ Close browser tabs after use (prevents accidental access)

On shared machines:

  • ✅ Always logout when done
  • ✅ Never use "Remember me"
  • ✅ Close all browser tabs and windows
  • ⚠️ Consider using private/incognito mode

On development servers:

  • ✅ Use strong passwords (public IP = higher risk)
  • ✅ Logout when done
  • ⚠️ Consider adding firewall rules (restrict to VPN)

Account Protection

Rate limiting (automatic):

  • 5 login attempts per 15 minutes (per IP)
  • Prevents brute force attacks
  • No action needed from you

Account lockout (automatic):

  • 10 failed login attempts → account locked for 1 hour
  • Prevents credential stuffing
  • Unlocks automatically after 1 hour
  • Counter resets on successful login

If you're locked out:

Error: "Account locked until 2025-10-18T13:00:00Z"

What to do:
1. Wait 1 hour for automatic unlock
2. Or, manually unlock (advanced: SQLite edit)

Browser Security

HTTP-only cookies:

  • Session cookie cannot be accessed by JavaScript
  • Protects against XSS attacks (malicious scripts can't steal session)
  • No action needed (automatic)

SameSite cookies:

  • Session cookie is not sent on cross-site requests
  • Protects against CSRF attacks (malicious sites can't make requests)
  • No action needed (automatic)

Cookie security summary:

Cookie: constants_session
HttpOnly: true (JavaScript can't access)
SameSite: Lax (No cross-site requests)
Secure: false (Local dev; true for HTTPS)
Expiration: 24h (Or 30 days if "remember me")

Troubleshooting

"Setup already completed" Error

Symptom: Trying to access /setup returns 403 error.

Cause: A user already exists in the database.

Solution:

  1. Go to /login instead
  2. Log in with existing credentials

If you forgot your password:

  • Manual reset (advanced): Edit SQLite database
  • Nuclear option: Delete database and restart (loses all data)
# Advanced: Manual password reset
docker compose exec api python reset_password.py <username>

# Nuclear option (loses ALL data)
docker compose down -v
docker compose up -d
# Navigate to /setup and create new account

"Rate limited" After Failed Logins

Symptom: Error message "Too many requests" after multiple failed login attempts.

Cause: Rate limiting triggered (5 attempts per 15 minutes exceeded).

Solution:

  1. Wait 15 minutes for rate limit to reset
  2. Verify your credentials (username and password)
  3. Try again

Prevention:

  • Double-check username and password before submitting
  • Use password manager to avoid typos
  • Don't repeatedly try different passwords

"Account locked until..." Error

Symptom: Error message "Account locked until 2025-10-18T13:00:00Z".

Cause: 10 failed login attempts triggered account lockout.

Solution:

  1. Wait 1 hour for automatic unlock
  2. Verify your credentials (you may have wrong password)

Manual unlock (advanced):

docker compose exec api python unlock_account.py <username>

Session Expired

Symptom: Accessing /bundles redirects to /login even though you were logged in earlier.

Cause: Session TTL expired (24 hours of inactivity).

Solution:

  1. Log in again
  2. Use "Remember me" for 30-day sessions (if desired)

Prevention:

  • Use "Remember me" checkbox for longer sessions
  • Access the UI regularly (keeps session alive)

Can't Access Bundles (401 Error)

Symptom: /bundles returns 401 Unauthorized error.

Cause: Session is invalid or expired.

Solution:

  1. Clear browser cookies
  2. Navigate to /login
  3. Log in again

Manual cookie clear:

Chrome/Edge: Settings > Privacy > Clear browsing data > Cookies
Firefox: Settings > Privacy > Clear Data > Cookies
Safari: Preferences > Privacy > Manage Website Data > Remove All

Browser Doesn't Remember Session

Symptom: Closing browser tab logs you out.

Possible causes:

  1. Browser in private/incognito mode:

    • Private mode doesn't persist cookies
    • Solution: Use normal browsing mode
  2. Browser set to clear cookies on exit:

    • Check browser privacy settings
    • Solution: Allow cookies for localhost:8888
  3. Docker restarted:

    • Redis is ephemeral (data lost on restart)
    • Solution: Expected behavior, log in again

Services Not Running

Symptom: Can't access http://localhost:8888.

Cause: Docker services not running.

Solution:

# Check service status
docker compose ps

# Start services
docker compose up -d

# Check logs for errors
docker compose logs web
docker compose logs api
docker compose logs redis

# Restart all services
docker compose restart

Next Steps

Now that authentication is set up:

  1. Create bundles: Navigate to /bundles and create your first bundle
  2. Add secrets: Import from .env or add keys manually
  3. Try CLI approval: Use the CLI to request secrets with approval workflow

Learn more: