# Login System Documentation ## Overview The MCP server includes a secure login system with email/password authentication that integrates with the User database. Upon successful login, users receive an encrypted session cookie that provides access to protected routes. ## Features - 🔐 **Email/Password Authentication** - User-friendly login with email addresses - 🍪 **Encrypted Cookies** - Secure session management with encrypted cookies - 🔒 **Account Security** - Account lockout after failed attempts, disabled account handling - 📝 **Audit Logging** - All login attempts are logged for security monitoring - 🎨 **Dark Mode UI** - Modern, accessible login interface ## Access The login page is available at: - **HTTPS**: `https://localhost/login` - **HTTP**: `http://localhost:4000/login` (if using HTTP docs server) ## Login Flow ``` 1. User visits protected route 2. No valid cookie → Redirected to /login 3. User enters email + password 4. Credentials validated against User database 5. On success: - Encrypted cookie set with user ID - User redirected to home page (/) - Cookie valid for 30 days 6. On failure: - Error message displayed - Failed attempt logged - Account locked after 5 failed attempts ``` ## Creating Users Users must be created programmatically using the User module: ```elixir # Create a user with email and password User.create( "johndoe", # Username "john@example.com", # Email "secure_password_123", # Password [123456, 11111] # Permission keys ) # Returns: {:ok, user_id} or {:error, reason} ``` ### Permission Keys Permission keys grant access to specific features: - `123456` → "index:view" (access to index pages) - `11111` → "admin:manage" (administrative access) See `AUTHORIZATION.md` for full permission list. ## Authentication Process ### 1. Email Lookup When a user submits the login form: 1. Email is used to find the user in the database 2. If user exists, their username is retrieved 3. Username + password are passed to authentication function ### 2. Credential Validation The User module validates: - User exists in database - Account is active (not disabled) - Account is not locked (failed attempt limit) - Password matches stored Argon2 hash ### 3. Session Creation On successful authentication: - User ID is encrypted using `Plug.Crypto.MessageEncryptor` - Encrypted value stored in secure cookie - Cookie flags: - `HttpOnly`: Prevents JavaScript access - `Secure`: HTTPS only - `SameSite=Lax`: CSRF protection - `max_age=30 days`: Long-lived session ### 4. IP Address Logging Client IP address is extracted and logged: - Checks `X-Forwarded-For` header (for proxies) - Falls back to `remote_ip` from connection - Stored in audit log for security monitoring ## Error Handling ### Invalid Credentials **Error**: "Invalid email or password" - User not found, OR - Password doesn't match **Note**: Same error message for both cases to prevent email enumeration. ### Account Locked **Error**: "Account is locked due to too many failed login attempts. Please try again later." - Account locked after 5 failed attempts - Lockout duration: 15 minutes - Unlock automatically after lockout period ### Account Disabled **Error**: "Account is disabled. Please contact support." - Account marked as inactive in database - Requires admin intervention to reactivate ## Cookie Encryption ### Encryption Details ```elixir # Secret key derivation secret = Plug.Crypto.KeyGenerator.generate( @secret_key_base, @cookie_salt, cache: Plug.Crypto.Keys ) # Encryption (login) encrypted = Plug.Crypto.MessageEncryptor.encrypt( user_id_string, secret, @cookie_salt ) # Decryption (authentication) {:ok, user_id_string} = Plug.Crypto.MessageEncryptor.decrypt( encrypted, secret, @cookie_salt ) ``` ### Security Considerations - ⚠️ **Change secret in production!** The default `@secret_key_base` is for development only - Cookie contains only user ID (not permissions or sensitive data) - Permissions loaded fresh from database on each request - Cookie tampering automatically detected and rejected ## Public Routes These routes are accessible without authentication: - `/login` - Login page (GET and POST) - `/docs` - Documentation index - `/docs/*` - All documentation files - `/set` - Legacy cookie setter (for development) All other routes require authentication. ## Integration with User Database ### Database Lookup The `get_user/1` function in the router: 1. Decrypts cookie to get user ID 2. Looks up user in SQLite database via `User.get_user/1` 3. Converts `permission_keys` array to map format 4. Returns user context with ID and permissions ```elixir # User in database: %{ id: 12345, username: "johndoe", email: "john@example.com", permission_keys: [123456, 11111] } # Converted to: %{ id: 12345, keys: %{123456 => true, 11111 => true} } ``` ### Authorization Check User context flows through the request pipeline: ``` Router → Manager → MyMCPServer → Tool Handler ↓ ↓ ↓ ↓ user user user user ``` Each tool checks if user has required permission before execution. ## Testing the Login System ### 1. Create Test User ```elixir iex> User.create("testuser", "test@example.com", "password123", [123456, 11111]) {:ok, 1234567890} ``` ### 2. Access Login Page Visit `https://localhost/login` in your browser. ### 3. Enter Credentials ``` Email: test@example.com Password: password123 ``` ### 4. Verify Success - Should redirect to home page - Cookie should be set (check browser DevTools) - Subsequent requests should be authenticated ### 5. Test Error Cases ```elixir # Wrong password User.authenticate("testuser", "wrongpass") # Returns: {:error, :invalid_credentials} # Lock account (5 failed attempts) for _ <- 1..5, do: User.authenticate("testuser", "wrong") User.authenticate("testuser", "password123") # Returns: {:error, :account_locked} # Wait 15 minutes or unlock manually User.unlock_account("testuser") ``` ## Audit Logging All login events are logged to the `user_audit_log` table: ```sql -- View recent login attempts SELECT * FROM user_audit_log WHERE action LIKE 'login%' ORDER BY created_at DESC LIMIT 10; -- View failed logins SELECT * FROM user_audit_log WHERE action = 'login_failed' ORDER BY created_at DESC; ``` ### Logged Events - `login_success` - Successful authentication - `login_failed` - Failed authentication with reason: - `invalid_credentials` - Wrong password or user not found - `account_locked` - Too many failed attempts - `account_disabled` - Account inactive ## Security Best Practices ### Production Deployment 1. **Change Secret Key** ```elixir # In production config or environment variable @secret_key_base System.get_env("SECRET_KEY_BASE") ``` 2. **Use HTTPS Only** - Ensure `secure: true` on cookies - Redirect HTTP to HTTPS - Use valid SSL certificates (not self-signed) 3. **Rate Limiting** - Consider adding rate limiting to /login endpoint - Prevent brute force attacks - Use tools like Hammer or PlugAttack 4. **Monitor Audit Logs** - Set up alerts for suspicious activity - Track failed login patterns - Monitor for account lockouts 5. **Password Policy** - Enforce strong passwords on user creation - Consider password complexity requirements - Implement password expiration if needed ### Password Storage Passwords are hashed using Argon2: - Industry-standard password hashing algorithm - OWASP recommended - Resistant to brute force attacks - Automatic salt generation **Never store plaintext passwords!** ## Troubleshooting ### Cookie Not Set After Login - Check browser console for errors - Verify HTTPS connection (cookies with `secure: true`) - Check cookie domain and path settings - Clear existing cookies and retry ### Redirect Loop - Check `require_authentication` plug configuration - Ensure `/login` is in public routes list - Verify cookie is being set correctly ### "Invalid session" Error - Cookie encryption/decryption mismatch - `@secret_key_base` changed since cookie creation - Cookie tampered with - Solution: Clear cookies and login again ### User Not Found After Login - Database connection issue - User deleted from database - Check logs for `User.get_user/1` errors ## Code Reference ### Files - `lib/my_mcp_server_router.ex` - Login routes and handlers - `GET /login` - Login page (line ~74) - `POST /login` - Login handler (line ~80) - `render_login_page/1` - Login UI (line ~193) - `handle_login/3` - Authentication logic (line ~245) - `authenticate_by_email/3` - Email-based auth (line ~296) - `lib/user.ex` - User management - `authenticate/4` - Username/password authentication (line ~200) - `find_by_email/2` - Email lookup (line ~363) - `get_user/2` - Fetch user by ID (line ~378) ### Related Documentation - `USER_MANAGEMENT.md` - Complete User module guide - `AUTHENTICATION.md` - Authentication system overview - `AUTHORIZATION.md` - Permission system details - `COOKIE_AUTH_QUICK_REF.md` - Cookie authentication reference ## Example: Complete Login Flow ```elixir # 1. Create user (one-time setup) {:ok, user_id} = User.create( "alice", "alice@company.com", "Alice123!@#", [123456, 11111] ) # 2. User visits https://localhost/page (protected route) # → No cookie → Redirected to /login # 3. User submits login form # POST /login # email=alice@company.com&password=Alice123!@# # 4. Router calls handle_login/3 # → authenticate_by_email/3 # → User.find_by_email("alice@company.com") # → User.authenticate("alice", "Alice123!@#", "192.168.1.1") # → Returns: {:ok, %{id: user_id, username: "alice", ...}} # 5. Set encrypted cookie cookie_value = encrypt_cookie_value(to_string(user_id)) # → "g2gCZAAEbm9uZW0AAAAgzR8..." # 6. Redirect to / # HTTP 302 Found # Set-Cookie: user=g2gCZAAEbm9uZW...; HttpOnly; Secure; ... # Location: / # 7. Browser requests / # Cookie: user=g2gCZAAEbm9uZW... # 8. Router requires authentication # → get_user/1 # → decrypt_cookie_value(encrypted) # → User.get_user(user_id) # → Returns: %{id: user_id, keys: %{123456 => true, 11111 => true}} # 9. Request proceeds with user context # User has access to protected resources! ``` ## Summary The login system provides: - ✅ Secure email/password authentication - ✅ Encrypted session cookies - ✅ Account security (lockouts, disabled accounts) - ✅ Audit logging for compliance - ✅ Integration with User database - ✅ Modern, accessible UI - ✅ Protection against common attacks For additional security features, see the User Management documentation.