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:
- Never leave your machine - No external authentication service
- Can't be breached remotely - No cloud provider to hack
- No third-party risk - No dependency on external services
- Air-gapped capable - Works fully offline
- 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:
- User is automatically logged in (session created in Redis)
- Session cookie is set in your browser (
constants_session) - You're redirected to
/login(where you're already authenticated) - 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:
- Session is created in Redis
- Session cookie is set in browser
- Failed login counter is reset to 0
- Last login timestamp is updated
- You're redirected to the
returnURL (or/bundlesby 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:
- Click the "Logout" button in the header
- Or navigate to:
http://localhost:8888/logout
What happens:
- Session is deleted from Redis (cannot be reused)
- Session cookie is cleared from browser
- 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:
- Browser sends session cookie automatically
- Web UI checks session validity (Redis lookup)
- If session is still valid (within TTL), you remain logged in
- 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 combinationsP@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:
- Go to
/logininstead - 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:
- Wait 15 minutes for rate limit to reset
- Verify your credentials (username and password)
- 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:
- Wait 1 hour for automatic unlock
- 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:
- Log in again
- 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:
- Clear browser cookies
- Navigate to
/login - 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:
-
Browser in private/incognito mode:
- Private mode doesn't persist cookies
- Solution: Use normal browsing mode
-
Browser set to clear cookies on exit:
- Check browser privacy settings
- Solution: Allow cookies for
localhost:8888
-
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:
- Create bundles: Navigate to
/bundlesand create your first bundle - Add secrets: Import from
.envor add keys manually - Try CLI approval: Use the CLI to request secrets with approval workflow
Learn more:
- API Reference: Authentication - Full API documentation
- Security: Authentication Model - Security details
- Guides: Authentication Workflows - Code examples