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

11 KiB

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:

{
	"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:

# 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:

    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.

    # 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

POST /api/auth/register
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "SecurePass123!",
  "first_name": "John",
  "last_name": "Doe"
}

Login

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:

{
	"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.

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

POST /api/auth/refresh
Content-Type: application/json

{
  "refreshToken": "eyJ..."
}

Logout

POST /api/auth/logout
Authorization: Bearer your-access-token

Email Verification

GET /api/auth/verify-email?token=verification_token

Profile Management

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:

    // 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:

    fetch("/api/protected-endpoint", {
    	headers: {
    		Authorization: `Bearer ${accessToken}`,
    		"Content-Type": "application/json",
    	},
    });
    
  3. Handle token refresh:

    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:

    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:

    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 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:

# 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.