358 lines
8.0 KiB
Markdown
358 lines
8.0 KiB
Markdown
# Login API Documentation
|
|
|
|
## Endpoint
|
|
```
|
|
POST /api/v1/auth/login
|
|
```
|
|
|
|
## Description
|
|
Login user with email or phone number. This endpoint authenticates users using either email address or phone number along with password.
|
|
|
|
## Current Implementation
|
|
|
|
### Frontend Code
|
|
|
|
**API Client** (`src/lib/api-client.ts`):
|
|
```typescript
|
|
export const adminApiHelpers = {
|
|
// Auth - uses publicApi (no token required)
|
|
login: (data: { email: string; password: string }) =>
|
|
publicApi.post('/auth/login', data),
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Login Page** (`src/pages/login/index.tsx`):
|
|
```typescript
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setIsLoading(true)
|
|
|
|
try {
|
|
const response = await adminApiHelpers.login({ email, password })
|
|
const { access_token, user } = response.data
|
|
|
|
// Check if user is admin
|
|
if (user.role !== 'ADMIN') {
|
|
toast.error("Access denied. Admin privileges required.")
|
|
return
|
|
}
|
|
|
|
// Store credentials
|
|
if (access_token) {
|
|
localStorage.setItem('access_token', access_token)
|
|
}
|
|
localStorage.setItem('user', JSON.stringify(user))
|
|
|
|
toast.success("Login successful!")
|
|
navigate(from, { replace: true })
|
|
} catch (error: any) {
|
|
const message = error.response?.data?.message || "Invalid email or password"
|
|
toast.error(message)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Request
|
|
|
|
### Headers
|
|
```
|
|
Content-Type: application/json
|
|
```
|
|
|
|
### Body (JSON)
|
|
|
|
**Option 1: Email + Password**
|
|
```json
|
|
{
|
|
"email": "admin@example.com",
|
|
"password": "your-password"
|
|
}
|
|
```
|
|
|
|
**Option 2: Phone + Password** (if backend supports)
|
|
```json
|
|
{
|
|
"phone": "+1234567890",
|
|
"password": "your-password"
|
|
}
|
|
```
|
|
|
|
### Example Request
|
|
```bash
|
|
curl -X POST https://api.yourdomain.com/api/v1/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "admin@example.com",
|
|
"password": "password123"
|
|
}'
|
|
```
|
|
|
|
## Response
|
|
|
|
### Success Response (200 OK)
|
|
|
|
**Option 1: With Access Token in Body** (localStorage fallback)
|
|
```json
|
|
{
|
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"user": {
|
|
"id": "user-id-123",
|
|
"email": "admin@example.com",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"role": "ADMIN",
|
|
"isActive": true,
|
|
"createdAt": "2024-01-01T00:00:00.000Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Option 2: With httpOnly Cookies** (recommended)
|
|
```http
|
|
HTTP/1.1 200 OK
|
|
Set-Cookie: access_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
|
|
Set-Cookie: refresh_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800
|
|
Content-Type: application/json
|
|
|
|
```
|
|
|
|
### Error Responses
|
|
|
|
**401 Unauthorized** - Invalid credentials
|
|
```json
|
|
{
|
|
"message": "Invalid email or password",
|
|
"statusCode": 401
|
|
}
|
|
```
|
|
|
|
**403 Forbidden** - Account inactive or not admin
|
|
```json
|
|
{
|
|
"message": "Account is inactive",
|
|
"statusCode": 403
|
|
}
|
|
```
|
|
|
|
**400 Bad Request** - Validation error
|
|
```json
|
|
{
|
|
"message": "Validation failed",
|
|
"errors": [
|
|
{
|
|
"field": "email",
|
|
"message": "Invalid email format"
|
|
}
|
|
],
|
|
"statusCode": 400
|
|
}
|
|
```
|
|
|
|
**429 Too Many Requests** - Rate limit exceeded
|
|
```json
|
|
{
|
|
"message": "Too many login attempts. Please try again later.",
|
|
"statusCode": 429,
|
|
"retryAfter": 900
|
|
}
|
|
```
|
|
|
|
**500 Internal Server Error** - Server error
|
|
```json
|
|
{
|
|
"message": "Internal server error",
|
|
"statusCode": 500
|
|
}
|
|
```
|
|
|
|
## Frontend Behavior
|
|
|
|
### 1. Form Validation
|
|
- Email: Required, valid email format
|
|
- Password: Required, minimum 8 characters
|
|
- Show/hide password toggle
|
|
|
|
### 2. Loading State
|
|
- Disable form during submission
|
|
- Show "Logging in..." button text
|
|
- Prevent multiple submissions
|
|
|
|
### 3. Success Flow
|
|
1. Validate response contains user data
|
|
2. Check if user.role === 'ADMIN'
|
|
3. Store access_token (if provided)
|
|
4. Store user data in localStorage
|
|
5. Show success toast
|
|
6. Redirect to dashboard or original destination
|
|
|
|
### 4. Error Handling
|
|
- Display user-friendly error messages
|
|
- Show toast notification
|
|
- Keep form enabled for retry
|
|
- Don't expose sensitive error details
|
|
|
|
### 5. Security Features
|
|
- HTTPS only in production
|
|
- httpOnly cookies support
|
|
- CSRF protection (SameSite cookies)
|
|
- Automatic token refresh
|
|
- Role-based access control
|
|
|
|
## Backend Requirements
|
|
|
|
### Must Implement
|
|
1. **Password Hashing**: bcrypt with salt rounds >= 12
|
|
2. **Rate Limiting**: 5 attempts per 15 minutes per IP
|
|
3. **Account Lockout**: Lock after 5 failed attempts
|
|
4. **Role Verification**: Ensure user.role === 'ADMIN'
|
|
5. **Active Status Check**: Verify user.isActive === true
|
|
6. **Token Generation**: JWT with proper expiration
|
|
7. **Audit Logging**: Log all login attempts
|
|
|
|
### Recommended
|
|
1. **httpOnly Cookies**: Store tokens in cookies, not response body
|
|
2. **Refresh Tokens**: Long-lived tokens for session renewal
|
|
3. **2FA Support**: Two-factor authentication
|
|
4. **IP Whitelisting**: Restrict admin access by IP
|
|
5. **Session Management**: Track active sessions
|
|
6. **Email Notifications**: Alert on new login
|
|
|
|
## Testing
|
|
|
|
### Manual Testing
|
|
```bash
|
|
# Test with valid credentials
|
|
curl -X POST http://localhost:3000/api/v1/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"admin@example.com","password":"password123"}'
|
|
|
|
# Test with invalid credentials
|
|
curl -X POST http://localhost:3000/api/v1/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"admin@example.com","password":"wrong"}'
|
|
|
|
# Test with non-admin user
|
|
curl -X POST http://localhost:3000/api/v1/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"user@example.com","password":"password123"}'
|
|
```
|
|
|
|
### Automated Testing
|
|
See `src/pages/login/__tests__/index.test.tsx` for component tests.
|
|
|
|
## Environment Variables
|
|
|
|
### Development (`.env`)
|
|
```env
|
|
VITE_BACKEND_API_URL=http://localhost:3000/api/v1
|
|
```
|
|
|
|
### Production (`.env.production`)
|
|
```env
|
|
VITE_BACKEND_API_URL=https://api.yourdomain.com/api/v1
|
|
```
|
|
|
|
## API Client Configuration
|
|
|
|
The login endpoint uses the `publicApi` instance which:
|
|
- Does NOT require authentication
|
|
- Does NOT send Authorization header
|
|
- DOES send cookies (`withCredentials: true`)
|
|
- DOES handle CORS properly
|
|
|
|
## Flow Diagram
|
|
|
|
```
|
|
User enters credentials
|
|
↓
|
|
Form validation
|
|
↓
|
|
POST /api/v1/auth/login
|
|
↓
|
|
Backend validates credentials
|
|
↓
|
|
Backend checks role === 'ADMIN'
|
|
↓
|
|
Backend generates tokens
|
|
↓
|
|
Backend returns user + tokens
|
|
↓
|
|
Frontend checks role === 'ADMIN'
|
|
↓
|
|
Frontend stores tokens
|
|
↓
|
|
Frontend redirects to dashboard
|
|
```
|
|
|
|
## Security Checklist
|
|
|
|
Backend:
|
|
- [ ] Passwords hashed with bcrypt
|
|
- [ ] Rate limiting enabled
|
|
- [ ] Account lockout implemented
|
|
- [ ] HTTPS enforced
|
|
- [ ] CORS configured properly
|
|
- [ ] httpOnly cookies used
|
|
- [ ] Audit logging enabled
|
|
- [ ] Input validation
|
|
- [ ] SQL injection prevention
|
|
|
|
Frontend:
|
|
- [x] HTTPS only in production
|
|
- [x] Cookie support enabled
|
|
- [x] Role verification
|
|
- [x] Error handling
|
|
- [x] Loading states
|
|
- [x] Form validation
|
|
- [x] Token storage
|
|
- [x] Automatic token refresh
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: "Network Error"
|
|
**Solution:** Check API URL in environment variables
|
|
|
|
### Issue: "CORS Error"
|
|
**Solution:** Backend must allow credentials and specific origin
|
|
|
|
### Issue: "Invalid credentials" for valid user
|
|
**Solution:** Check backend password hashing and comparison
|
|
|
|
### Issue: "Access denied" for admin user
|
|
**Solution:** Verify user.role === 'ADMIN' in database
|
|
|
|
### Issue: Token not persisting
|
|
**Solution:** Check if backend is setting httpOnly cookies or returning access_token
|
|
|
|
## Related Documentation
|
|
|
|
- [Authentication Setup](./AUTHENTICATION.md)
|
|
- [API Standards](./API_STANDARDS.md)
|
|
- [Security Checklist](./SECURITY_CHECKLIST.md)
|
|
- [Backend Requirements](./SECURITY_CHECKLIST.md#backend-security)
|
|
|
|
## Support
|
|
|
|
For issues with login:
|
|
1. Check browser console for errors
|
|
2. Check network tab for API response
|
|
3. Verify environment variables
|
|
4. Check backend logs
|
|
5. Test with curl/Postman
|
|
|