formies/AUTHENTICATION_SETUP.md
Mohamad.Elsena 2927013a6d Update environment configuration, add API documentation, and implement user authentication system
- Updated `.env` and added `.env.test` for environment variables.
- Introduced API documentation in `API_DOCUMENTATION.md`.
- Added authentication setup guide in `AUTHENTICATION_SETUP.md`.
- Implemented user authentication with JWT and email verification.
- Created new routes for user management and form submissions.
- Added middleware for API key authentication and error handling.
- Set up Redis for rate limiting and notifications.
- Removed obsolete files and configurations related to the previous Rust implementation.
2025-05-28 11:18:35 +02:00

433 lines
11 KiB
Markdown

# Authentication System Setup Guide
## Overview
This guide will help you set up the robust user authentication and authorization system for your Formies SaaS application. The system includes:
- **JWT-based authentication** with access and refresh tokens
- **Email verification** with automated emails
- **Password reset** functionality
- **Role-based authorization** (user, admin, super_admin)
- **Account security** features (failed login tracking, account locking)
- **Rate limiting** to prevent abuse
- **Session management** with token blacklisting
## Required Dependencies
The following packages have been added to your `package.json`:
```json
{
"bcryptjs": "^2.4.3",
"express-rate-limit": "^7.1.5",
"express-session": "^1.17.3",
"express-validator": "^7.0.1",
"jsonwebtoken": "^9.0.2",
"nodemailer": "^6.9.8",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0"
}
```
## Environment Variables
Create a `.env` file with the following variables:
```env
# Database Configuration
DB_HOST=localhost
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_NAME=forms_db
# JWT Configuration (REQUIRED)
JWT_SECRET=your-super-secret-jwt-key-at-least-32-characters-long
JWT_ISSUER=formies
JWT_AUDIENCE=formies-users
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d
# Session Configuration
SESSION_SECRET=your-session-secret-key-change-this-in-production
# Application Configuration
APP_URL=http://localhost:3000
NODE_ENV=development
PORT=3000
# SMTP Email Configuration (Optional but recommended)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
SMTP_FROM_EMAIL=noreply@yourdomain.com
# Notification Configuration
NTFY_ENABLED=true
NTFY_TOPIC_URL=https://ntfy.sh/your-topic
```
## Database Setup
1. **Install dependencies:**
```bash
npm install
```
2. **Update your database** by running the updated `init.sql`:
This script will create all necessary tables, including the `users` table with a default `super_admin` account (`admin@formies.local`).
The initial password for this `super_admin` is NOT set in the `init.sql` script. The `must_change_password` flag will be set to `TRUE`.
```bash
# If using Docker
docker-compose down
docker-compose up -d
# Or manually run the SQL file in your MySQL database
mysql -u your_user -p your_database < init.sql
```
If the login is for the `super_admin` (`admin@formies.local`) and it's their first login (`must_change_password` is `TRUE` on the user object returned from the `/login` attempt, even if successful), the API might return a successful login response but the client should check for this flag. Alternatively, the `/login` endpoint itself has been modified to return a `403 Forbidden` response with `code: "MUST_CHANGE_PASSWORD"` directly if this condition is met. The client application should handle this response and prompt the user to use the `/force-change-password` endpoint.
## API Endpoints
### Authentication Endpoints
All authentication endpoints are prefixed with `/api/auth`:
#### Registration
```http
POST /api/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe"
}
```
#### Login
```http
POST /api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass123!"
}
Response:
{
"success": true,
"message": "Login successful",
"data": {
"user": { ... },
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"accessTokenExpiresAt": "2024-01-01T00:00:00.000Z",
"refreshTokenExpiresAt": "2024-01-07T00:00:00.000Z",
"tokenType": "Bearer"
}
}
```
**Super Admin First Login:**
If the login attempt is for the `super_admin` (`admin@formies.local`) and the `must_change_password` flag is `TRUE` for this user, the `/api/auth/login` endpoint will return a `403 Forbidden` response with the following structure:
```json
{
"success": false,
"message": "Password change required.",
"code": "MUST_CHANGE_PASSWORD",
"data": {
"user": {
"id": "user_id",
"uuid": "user_uuid",
"email": "admin@formies.local",
"role": "super_admin"
}
}
}
```
The client application should detect this `code: "MUST_CHANGE_PASSWORD"` and guide the user to set a new password using the endpoint below. The `accessToken` and `refreshToken` will NOT be issued in this case. The client will need to make a subsequent call to `/api/auth/force-change-password` using a temporary mechanism if required, or by having the user log in, get the 403, then use a password change form that calls the next endpoint. For the current implementation, the super_admin will receive a standard JWT upon providing correct credentials (even if `must_change_password` is true), and this token should be used for the `/force-change-password` call.
#### Force Password Change
This endpoint is used when a user, particularly the initial `super_admin`, needs to set their password for the first time or has been flagged for a mandatory password update.
```http
POST /api/auth/force-change-password
Authorization: Bearer your-access-token-from-login-attempt
Content-Type: application/json
{
"newPassword": "ANewStrongPassword123!"
}
Response (on success):
{
"success": true,
"message": "Password changed successfully. Please log in again with your new password."
}
```
After a successful password change using this endpoint:
- The user's password is updated.
- The `must_change_password` flag is set to `FALSE`.
- All other active sessions for this user are invalidated for security.
- The user will need to log in again with their new password to obtain new session tokens.
#### Token Refresh
```http
POST /api/auth/refresh
Content-Type: application/json
{
"refreshToken": "eyJ..."
}
```
#### Logout
```http
POST /api/auth/logout
Authorization: Bearer your-access-token
```
#### Email Verification
```http
GET /api/auth/verify-email?token=verification_token
```
#### Profile Management
```http
GET /api/auth/profile
Authorization: Bearer your-access-token
PUT /api/auth/profile
Authorization: Bearer your-access-token
Content-Type: application/json
{
"first_name": "John",
"last_name": "Doe",
"email": "newemail@example.com"
}
```
## Security Features
### Password Requirements
- Minimum 8 characters
- At least one lowercase letter
- At least one uppercase letter
- At least one number
- At least one special character (@$!%\*?&)
### Account Security
- Failed login attempts are tracked
- Account locks after 5 failed attempts for 30 minutes
- Email verification required for new accounts
- JWT tokens are tracked and can be revoked
### Rate Limiting
- **Login attempts:** 5 per 15 minutes per IP/email
- **Registration:** 3 per hour per IP
- **Password reset:** 3 per hour per IP/email
## Using the Authentication System
### Frontend Integration
1. **Store tokens securely:**
```javascript
// Store in secure httpOnly cookies or localStorage (less secure)
localStorage.setItem("accessToken", response.data.accessToken);
localStorage.setItem("refreshToken", response.data.refreshToken);
```
2. **Include token in requests:**
```javascript
fetch("/api/protected-endpoint", {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
```
3. **Handle token refresh:**
```javascript
async function refreshToken() {
const refreshToken = localStorage.getItem("refreshToken");
const response = await fetch("/api/auth/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken }),
});
if (response.ok) {
const data = await response.json();
localStorage.setItem("accessToken", data.data.accessToken);
return data.data.accessToken;
} else {
// Redirect to login
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
window.location.href = "/login";
}
}
```
### Backend Integration
1. **Protect routes with authentication:**
```javascript
const {
requireAuth,
requireAdmin,
} = require("./src/middleware/authMiddleware");
// Require authentication
router.get("/protected", requireAuth, (req, res) => {
res.json({ user: req.user });
});
// Require admin role
router.get("/admin-only", requireAdmin, (req, res) => {
res.json({ message: "Admin access granted" });
});
```
2. **Check resource ownership:**
```javascript
const {
requireOwnershipOrAdmin,
} = require("./src/middleware/authMiddleware");
router.get(
"/forms/:id",
requireOwnershipOrAdmin(async (req) => {
const form = await Form.findById(req.params.id);
return form.user_id;
}),
(req, res) => {
// User can only access their own forms or admin can access all
}
);
```
## Migration from Basic Auth
The system maintains backward compatibility with your existing basic auth. To fully migrate:
1. **Update admin routes** to use the new authentication system
2. **Create admin users** in the database with appropriate roles
3. **Remove basic auth middleware** once migration is complete
## Default Admin Account
A default super admin account is created automatically:
- **Email:** admin@formies.local
- **Password:** admin123 (change immediately!)
## Email Configuration
For email verification and password reset to work, configure SMTP settings:
### Gmail Setup
1. Enable 2-factor authentication
2. Generate an app password
3. Use the app password in `SMTP_PASS`
### Other Providers
- **Outlook:** smtp-mail.outlook.com:587
- **SendGrid:** smtp.sendgrid.net:587
- **Mailgun:** smtp.mailgun.org:587
## Production Considerations
1. **Use strong secrets:** Generate random JWT_SECRET and SESSION_SECRET
2. **Enable HTTPS:** Set `NODE_ENV=production` and use SSL certificates
3. **Use Redis for sessions:** Replace memory sessions with Redis
4. **Monitor rate limits:** Adjust rate limiting based on usage patterns
5. **Backup token sessions:** Consider database-backed session storage
## Troubleshooting
### Common Issues
1. **JWT_SECRET not set:**
```
WARNING: JWT_SECRET not set. Authentication will not work properly.
```
Solution: Add JWT_SECRET to your .env file
2. **Email service not working:**
```
Email service not configured. Set SMTP environment variables.
```
Solution: Configure SMTP settings in .env file
3. **Database connection errors:**
- Verify database credentials
- Ensure database exists
- Check if init.sql has been run
4. **Token validation errors:**
- Check if JWT_SECRET matches between requests
- Verify token hasn't expired
- Ensure token is properly formatted in Authorization header
## Testing the System
Use these curl commands to test the authentication endpoints:
```bash
# Register a new user
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"TestPass123!","first_name":"Test","last_name":"User"}'
# Login
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"TestPass123!"}'
# Access protected endpoint
curl -X GET http://localhost:3000/api/auth/profile \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
This authentication system provides enterprise-grade security for your SaaS application while maintaining flexibility and ease of use.