This document outlines the technical specifications for an authentication system designed for a professional workflow tool. Leveraging Express.js and MongoDB, it integrates JWT for stateless authentication, OAuth2 for Google and GitHub logins, and optional TOTP-based Multi-Factor Authentication. The specification covers architectural design, API endpoints, data models, robust error handling, comprehensive testing, critical security considerations, and a practical deployment guide.
The authentication system follows a microservice-oriented architecture, with a clear separation of concerns.
Components:
speakeasy or otp-generator.Flows:
All API endpoints will be prefixed with /api/v1/auth and return JSON responses.
2.1. Local Authentication
POST /api/v1/auth/register* Description: Registers a new user with email and password.
* Request Body: { email, password, firstName, lastName }
* Response: { message: 'User registered successfully', userId: '...' }
POST /api/v1/auth/login* Description: Authenticates a user with email and password.
* Request Body: { email, password, mfaCode? }
* Response: { accessToken, refreshToken, user: { id, email, firstName, lastName, mfaEnabled } }
POST /api/v1/auth/refresh-token* Description: Exchanges a valid refresh token for a new access token.
* Request Body: { refreshToken }
* Response: { accessToken }
POST /api/v1/auth/logout* Description: Invalidates the provided refresh token, effectively logging out the user.
* Request Body: { refreshToken }
* Response: { message: 'Logged out successfully' }
GET /api/v1/auth/me (Protected)* Description: Retrieves the profile of the authenticated user.
* Response: { id, email, firstName, lastName, roles, mfaEnabled, ... }
POST /api/v1/auth/forgot-password* Description: Initiates password reset process by sending a reset link to the user's email.
* Request Body: { email }
* Response: { message: 'Password reset link sent to email' }
POST /api/v1/auth/reset-password* Description: Resets the user's password using a valid reset token.
* Request Body: { token, newPassword }
* Response: { message: 'Password reset successfully' }
2.2. OAuth2 Authentication
GET /api/v1/auth/google* Description: Initiates Google OAuth2 flow.
* Response: Redirects to Google authentication page.
GET /api/v1/auth/google/callback* Description: Google OAuth2 callback endpoint. Handles user data retrieval and token generation.
* Response: Redirects to client application with accessToken and refreshToken (e.g., via query params or cookie).
GET /api/v1/auth/github* Description: Initiates GitHub OAuth2 flow.
* Response: Redirects to GitHub authentication page.
GET /api/v1/auth/github/callback* Description: GitHub OAuth2 callback endpoint. Handles user data retrieval and token generation.
* Response: Redirects to client application with accessToken and refreshToken.
2.3. Multi-Factor Authentication (TOTP)
POST /api/v1/auth/mfa/setup (Protected)* Description: Generates a new TOTP secret and returns a QR code URL for user setup.
* Response: { qrCodeUrl, secret }
POST /api/v1/auth/mfa/verify (Protected)* Description: Verifies a TOTP code against the generated secret during setup.
* Request Body: { secret, code }
* Response: { verified: true/false }
POST /api/v1/auth/mfa/enable (Protected)* Description: Enables MFA for the authenticated user after successful verification.
* Request Body: { secret, code }
* Response: { message: 'MFA enabled successfully' }
POST /api/v1/auth/mfa/disable (Protected)* Description: Disables MFA for the authenticated user (requires password or current TOTP code for confirmation).
* Request Body: { password | code }
* Response: { message: 'MFA disabled successfully' }
POST /api/v1/auth/mfa/authenticate* Description: Verifies TOTP code during login for users with MFA enabled.
* Request Body: { userId, code }
* Response: { message: 'MFA code verified' } (This would typically be part of the /login flow, but can be a separate step for re-authentication challenges).
The following MongoDB schemas will be implemented using Mongoose.
3.1. User Model
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true
},
password: {
type: String, // Hashed password, optional for OAuth-only users
minlength: 8
},
firstName: {
type: String,
required: true,
trim: true
},
lastName: {
type: String,
required: true,
trim: true
},
roles: [{
type: String, // e.g., 'user', 'admin', 'manager'
enum: ['user', 'admin', 'manager'],
default: 'user'
}],
isVerified: {
type: Boolean,
default: false // For email verification
},
verificationToken: String,
verificationTokenExpires: Date,
passwordResetToken: String,
passwordResetExpires: Date,
oauthProviders: [{
provider: { type: String, enum: ['google', 'github'] },
providerId: String,
accessToken: String, // Optional, for accessing provider APIs
refreshToken: String // Optional
}],
mfaEnabled: {
type: Boolean,
default: false
},
mfaSecret: {
type: String, // Encrypted TOTP secret
select: false // Do not return by default
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// Pre-save hook for password hashing and updatedAt
UserSchema.pre('save', async function(next) {
if (this.isModified('password') && this.password) {
this.password = await bcrypt.hash(this.password, 10);
}
this.updatedAt = Date.now();
next();
});
// Method to compare passwords
UserSchema.methods.comparePassword = async function(candidatePassword) {
return bcrypt.compare(candidatePassword, this.password);
};
3.2. RefreshToken Model
const RefreshTokenSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
token: {
type: String,
required: true,
unique: true
},
expiresAt: {
type: Date,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
ipAddress: String,
userAgent: String
});
// Index for efficient lookup and expiry
RefreshTokenSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
A centralized error handling strategy will be implemented using Express.js middleware to ensure consistent and informative error responses.
4.1. Custom Error Classes:
AuthError (401 Unauthorized, 403 Forbidden): For authentication/authorization failures.ValidationError (400 Bad Request, 422 Unprocessable Entity): For invalid input data (e.g., missing fields, incorrect format).NotFoundError (404 Not Found): When a requested resource does not exist.ConflictError (409 Conflict): For resource conflicts (e.g., registering with an existing email).InternalServerError (500 Internal Server Error): For unexpected server-side issues.4.2. Consistent Error Response Format:
All error responses will adhere to a standard JSON structure:
{
"status": "error",
"message": "A human-readable error message.",
"code": "AUTH_001", // An internal error code for programmatic handling
"statusCode": 401, // HTTP status code
"details": [ // Optional: specific validation errors or additional info
{ "field": "email", "message": "Email is required." },
{ "field": "password", "message": "Password must be at least 8 characters." }
]
}
4.3. Centralized Error Middleware:
An Express.js error handling middleware will catch all errors thrown by routes and other middleware. It will:
ValidationError) that are safe to expose to the client and programming errors (e.g., TypeError) which should be masked as generic 500 errors in production.4.4. Input Validation:
express-validator, Joi) to validate all incoming request bodies and query parameters against defined schemas.ValidationError instances.A multi-faceted testing strategy will be employed to ensure the reliability, security, and performance of the authentication system.
5.1. Unit Tests (Jest)
sinon or Jest's built-in mocks for mocking dependencies.5.2. Integration Tests (Supertest & Jest)
* Verify user registration creates a record in MongoDB.
* Test login with correct/incorrect credentials.
* Ensure protected endpoints return 401 without a valid token.
* Test refresh token rotation.
* Verify OAuth callback processes user data correctly.
5.3. End-to-End (E2E) Tests (Cypress/Playwright)
* Full user registration, email verification, and login flow.
* Login via Google/GitHub and subsequent access to a protected resource.
* MFA setup and login with TOTP.
* Password reset flow.
5.4. Security Testing
5.5. Performance Testing (JMeter/K6)
Security is paramount for an authentication system. The following measures will be implemented:
6.1. Password Management:
bcrypt with a sufficient work factor (e.g., 10-12 rounds).bcrypt for each password.6.2. JWT and Token Management:
6.3. OAuth2 Security:
state parameter during OAuth redirects to prevent Cross-Site Request Forgery (CSRF) attacks.6.4. Multi-Factor Authentication (TOTP):
6.5. General Security Practices:
This section outlines the steps and considerations for deploying the authentication system.
7.1. Prerequisites:
7.2. Environment Setup:
.env file based on a .env.example template. This file will contain: * PORT: Application port (e.g., 3000)
* MONGODB_URI: Connection string for MongoDB
* JWT_SECRET: Secret key for signing JWTs (strong, random string)
* JWT_ACCESS_TOKEN_EXPIRATION: e.g., 15m
* JWT_REFRESH_TOKEN_EXPIRATION: e.g., 7d
* OAUTH_GOOGLE_CLIENT_ID, OAUTH_GOOGLE_CLIENT_SECRET, OAUTH_GOOGLE_CALLBACK_URL
* OAUTH_GITHUB_CLIENT_ID, OAUTH_GITHUB_CLIENT_SECRET, OAUTH_GITHUB_CALLBACK_URL
* EMAIL_SERVICE_API_KEY, EMAIL_SENDER_ADDRESS (for password resets, email verification)
* MFA_SECRET_ENCRYPTION_KEY: Key for encrypting MFA secrets in DB
npm install or yarn install to install dependencies.7.3. Local Development & Testing (Docker Compose):
docker-compose.yml file to spin up the Express.js application and a local MongoDB instance for development and testing.docker-compose up --build to build and start services.7.4. Production Deployment Strategy:
Dockerfile for the Express.js application to ensure consistent environments across development and production.* Compute: AWS EC2, AWS Fargate/ECS, Google Cloud Run, Azure App Service, Kubernetes (EKS, GKE, AKS).
* Database: MongoDB Atlas (managed service) is highly recommended for production due to its scalability, reliability, and security features.
* Use Nginx or a cloud-native load balancer (e.g., AWS ALB, GCP Load Balancer) to distribute traffic, handle SSL termination, and provide basic security features.
* Configure health checks for the Express.js instances.
* Build Stage: Lint, run unit/integration tests, build Docker image.
* Deploy Stage: Push Docker image to a container registry (e.g., Docker Hub, AWS ECR), deploy to staging/production environment.
* Integrate with a logging service (e.g., AWS CloudWatch, Google Cloud Logging, ELK Stack) for centralized log collection.
* Set up monitoring and alerting for application health, performance metrics (CPU, memory, request latency), and error rates (e.g., Prometheus/Grafana, Datadog).
7.5. Post-Deployment:
\n