12 KiB
Login API Implementation
This document describes the complete implementation of the user login feature for the MaplePress frontend, integrated with the MaplePress backend API.
Overview
The login feature allows existing users to authenticate with their email and password credentials. Upon successful login, users receive authentication tokens and are automatically logged in to their dashboard.
Backend API Endpoint
Endpoint: POST /api/v1/login
Authentication: None required (public endpoint)
Documentation: /cloud/maplepress-backend/docs/API.md (lines 168-228)
Request Structure
{
"email": "user@example.com",
"password": "SecurePassword123!"
}
Response Structure
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"user_email": "user@example.com",
"user_name": "John Doe",
"user_role": "user",
"tenant_id": "650e8400-e29b-41d4-a716-446655440000",
"session_id": "750e8400-e29b-41d4-a716-446655440000",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"access_expiry": "2024-10-24T12:15:00Z",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_expiry": "2024-10-31T00:00:00Z",
"login_at": "2024-10-24T00:00:00Z"
}
Key Differences from Registration
The login endpoint differs from registration in several ways:
- Simpler Request: Only email and password required
- No Tenant Details in Response: Tenant name/slug not included
- Login Timestamp: Includes
login_atinstead ofcreated_at - Existing Session: Authenticates existing user, doesn't create new account
Frontend Implementation
1. LoginService (src/services/API/LoginService.js)
Handles direct communication with the backend login API.
Key Features:
- Request validation (required fields)
- Request body formatting (snake_case for backend)
- Response transformation (camelCase for frontend)
- User-friendly error message mapping
- Rate limit error handling
- Account lockout detection
Methods:
login(credentials)- Main login method
Usage:
import LoginService from './services/API/LoginService';
const response = await LoginService.login({
email: "user@example.com",
password: "SecurePassword123!",
});
2. AuthManager Enhancement (src/services/Manager/AuthManager.js)
Updated to support login functionality while maintaining registration support.
New/Updated Methods:
login(email, password)- Login and store auth datastoreAuthData(authResponse)- Updated to handle optional tenant fields
Key Features:
- Handles both registration and login responses
- Gracefully handles missing tenant name/slug from login
- Maintains same token storage mechanism
- Consistent session management
Login Flow:
const authManager = useAuth().authManager;
// Login
await authManager.login("user@example.com", "password123");
// Check authentication
if (authManager.isAuthenticated()) {
const user = authManager.getUser();
const tenant = authManager.getTenant();
}
3. Login Page (src/pages/Auth/Login.jsx)
Simple and clean login form ready for production use.
Form Fields:
- Email Address (required)
- Password (required)
Features:
- Email validation (HTML5 + backend)
- Loading state during submission
- Error message display
- Navigation to registration
- Navigation back to home
Data Flow
User enters credentials
↓
Login.jsx validates data
↓
AuthManager.login(email, password)
↓
LoginService.login(credentials)
↓
HTTP POST to /api/v1/login
↓
Backend validates credentials
↓
Backend returns tokens and user data
↓
LoginService transforms response
↓
AuthManager stores tokens in localStorage
↓
User redirected to /dashboard
Validation Rules
Frontend Validation
- Email: Required, valid email format (HTML5)
- Password: Required (no client-side length check for login)
Backend Validation
Backend performs:
- Email format validation
- Email normalization (lowercase, trim)
- Password verification against stored hash
- Rate limiting checks
- Account lockout checks
Error Handling
Error Messages
Errors are mapped to user-friendly messages:
| Backend Error | Frontend Message |
|---|---|
| "Invalid email or password" | "Invalid email or password. Please try again." |
| "X attempts remaining" | Original message with attempt counter |
| "locked" or "Too many" | "Account temporarily locked due to too many failed attempts. Please try again later." |
| "invalid email" | "Invalid email address." |
Rate Limiting & Account Lockout
The backend implements sophisticated rate limiting:
Per-IP Rate Limiting:
- Limit: Multiple attempts per 15 minutes
- Response:
429 Too Many Requests - Header:
Retry-After: 900(15 minutes)
Per-Account Rate Limiting:
- Limit: 5 failed attempts
- Lockout: 30 minutes after 5 failures
- Response:
429 Too Many Requests - Header:
Retry-After: 1800(30 minutes) - Warning: Shows remaining attempts when ≤ 3
Behavior:
- First 2 failures: Generic error message
- 3rd-5th failure: Shows remaining attempts
- After 5th failure: Account locked for 30 minutes
- Successful login: Resets all counters
Security Event Logging
Backend logs security events for:
- Failed login attempts (with email hash)
- Successful logins (with email hash)
- IP rate limit exceeded
- Account lockouts
Security Features
- Token Storage: Tokens stored in localStorage
- Token Expiry: Automatic expiry checking
- Rate Limiting: Backend enforces rate limits
- Account Lockout: Protects against brute force
- Email Normalization: Prevents bypass via casing
- Secure Logging: PII never logged, only hashes
- Password Hash Verification: Uses bcrypt on backend
Token Lifetime
- Access Token: 15 minutes
- Refresh Token: 7 days
- Session: 14 days (max inactivity)
Testing the Login Flow
1. Prerequisites
Ensure you have a registered user. If not, register first:
# Navigate to registration
http://localhost:5173/register
# Or use curl to register via backend
curl -X POST http://localhost:8000/api/v1/register \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "SecurePass123!",
"name": "Test User",
"tenant_name": "Test Corp",
"tenant_slug": "test-corp",
"agree_terms_of_service": true
}'
2. Start Services
# Backend
cd cloud/maplepress-backend
task dev
# Frontend
cd web/maplepress-frontend
npm run dev
3. Navigate to Login
Open browser to: http://localhost:5173/login
4. Fill Form
- Email: test@example.com
- Password: SecurePass123!
5. Submit
Click "Sign In" button
6. Expected Result
- Loading state appears ("Signing in...")
- Request sent to backend
- Tokens stored in localStorage
- User redirected to
/dashboard - Dashboard shows user information
7. Verify in Browser Console
// Check stored tokens
localStorage.getItem('maplepress_access_token')
localStorage.getItem('maplepress_user')
localStorage.getItem('maplepress_tenant')
// Check service instance
window.maplePressServices.authManager.isAuthenticated() // true
window.maplePressServices.authManager.getUser()
// Returns: { id: "...", email: "test@example.com", name: "Test User", role: "..." }
window.maplePressServices.authManager.getTenant()
// Returns: { id: "...", name: null, slug: null }
// Note: name/slug are null because login endpoint doesn't provide them
Error Testing
Test Invalid Credentials
// In login form, enter:
Email: test@example.com
Password: WrongPassword123
// Expected: "Invalid email or password. Please try again."
Test Rate Limiting
// Attempt login 3 times with wrong password
// Expected on 3rd attempt: "Invalid email or password. 2 attempts remaining before account lockout."
// Attempt 2 more times
// Expected on 5th attempt: "Account temporarily locked due to too many failed attempts. Please try again later."
Test Missing Fields
// Leave email blank
// Expected: Browser validation error (HTML5 required)
// Leave password blank
// Expected: Browser validation error (HTML5 required)
Differences from Registration
| Feature | Registration | Login |
|---|---|---|
| Request Fields | 10+ fields | 2 fields only |
| Response Fields | Includes tenant name/slug | No tenant name/slug |
| Timestamp | created_at |
login_at |
| Creates User | Yes | No |
| Creates Tenant | Yes | No |
| Terms Agreement | Required | Not needed |
| Organization Info | Required | Not needed |
Token Storage Compatibility
Both registration and login use the same storage mechanism:
// Storage Keys (7 total)
maplepress_access_token // JWT access token
maplepress_refresh_token // JWT refresh token
maplepress_access_expiry // ISO date string
maplepress_refresh_expiry // ISO date string
maplepress_user // JSON: {id, email, name, role}
maplepress_tenant // JSON: {id, name, slug}
maplepress_session_id // Session UUID
Note: After login, tenant.name and tenant.slug will be null. This is expected behavior. If needed, fetch tenant details separately using the /api/v1/tenants/{id} endpoint.
Session Persistence
Authentication state persists across:
- Page refreshes
- Browser restarts (if localStorage not cleared)
- Tab changes
Session is cleared on:
- User logout
- Token expiry detection
- Manual localStorage clear
Integration with Dashboard
The dashboard automatically checks authentication:
// Dashboard.jsx
useEffect(() => {
if (!authManager.isAuthenticated()) {
navigate("/login");
return;
}
const userData = authManager.getUser();
setUser(userData);
}, [authManager, navigate]);
API Client Configuration
The ApiClient automatically handles:
- JSON content-type headers
- Request/response transformation
- Error parsing
- Authentication headers (for protected endpoints)
Note: Login endpoint doesn't require authentication headers, but subsequent API calls will use the stored access token.
Future Enhancements
Planned Features
- "Remember Me" checkbox (longer session)
- "Forgot Password" link
- Social authentication (Google, GitHub)
- Two-factor authentication (2FA/TOTP)
- Session management (view active sessions)
- Device fingerprinting
- Suspicious login detection
Security Improvements
- CSRF token implementation
- HTTP-only cookie option
- Session fingerprinting
- Geolocation tracking
- Email notification on new login
- Passwordless login option
Troubleshooting
"Invalid email or password" but credentials are correct
Possible causes:
- Email case sensitivity - backend normalizes to lowercase
- Extra whitespace in password field
- User not yet registered
- Account locked due to previous failed attempts
Solution:
- Wait 30 minutes if account is locked
- Try registering if user doesn't exist
- Check browser console for detailed errors
Tokens not being stored
Possible causes:
- localStorage disabled in browser
- Private/Incognito mode restrictions
- Browser extension blocking storage
Solution:
- Enable localStorage in browser settings
- Use regular browser window
- Disable blocking extensions temporarily
Redirected back to login after successful login
Possible causes:
- Token expiry detection triggered
- Token format invalid
- localStorage cleared between operations
Solution:
- Check browser console for errors
- Verify localStorage contains tokens
- Check token expiry dates
Related Files
Created Files
src/services/API/LoginService.js
Modified Files
src/services/Manager/AuthManager.js
src/pages/Auth/Login.jsx
Backend Reference Files
cloud/maplepress-backend/docs/API.md
cloud/maplepress-backend/internal/interface/http/dto/gateway/login_dto.go
cloud/maplepress-backend/internal/interface/http/handler/gateway/login_handler.go
Related Documentation
- REGISTRATION_API.md - Registration implementation
- ARCHITECTURE.md - Frontend architecture overview
- README.md - Getting started guide
- Backend API Documentation - Complete API reference
Support
For issues:
- Check backend logs:
docker logs mapleopentech_backend - Check browser console for errors
- Verify backend is running on port 8000
- Test backend endpoint directly with curl
- Check rate limiting status (wait 30 minutes if locked)