User Authentication
Authentication routes for signup, login, and OAuth.
POST /api/user/signup
Create a new user account after purchasing a plan.
Request
POST /api/user/signup
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"password": "securepassword123",
"repeatPassword": "securepassword123"
}Response
Success (201):
{
"user": {
"id": "507f1f77bcf86cd799439011",
"name": "John Doe",
"email": "john@example.com",
"plan": "pro"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Errors:
400- Missing fields or passwords don’t match403- No purchase found for email409- User already exists
Flow
- Validates all fields are present
- Checks passwords match
- Looks for existing user with email
- Verifies billing record exists (from Stripe purchase)
- Hashes password with pbkdf2 (100,000 iterations)
- Creates User document
- Creates Identity document (local provider)
- Links Billing to User
- Generates JWT token
- Returns user and token
Users must purchase a plan before signing up. The Stripe checkout creates a Billing record that signup links to.
POST /api/user/auth
Login with email and password.
Request
POST /api/user/auth
Content-Type: application/json
{
"email": "john@example.com",
"password": "securepassword123"
}Response
Success (200):
{
"user": {
"id": "507f1f77bcf86cd799439011",
"name": "John Doe",
"email": "john@example.com",
"plan": "pro"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Errors:
400- Missing email or password401- Invalid credentials404- User not found
Flow
- Finds Identity by email and LOCAL provider
- Verifies password hash matches
- Loads User document
- Loads Billing to get current plan
- Generates JWT token
- Returns user and token
Password Verification
Passwords are verified using the Node.js crypto library. The system:
- Decodes the user’s unique salt
- Hashes the input password using pbkdf2 with the salt
- Compares the resulting hash with the stored hash
This ensures passwords are never stored in plain text and are protected against rainbow table attacks.
POST /api/user/google-auth
Login or signup with Google OAuth.
Request
POST /api/user/google-auth
Content-Type: application/json
{
"credential": "google-jwt-token-from-client"
}Response
Same as signup/login - returns user and accessToken.
Flow
- Verifies Google token with Google API
- Extracts user info (email, name, picture)
- Looks for existing Identity with Google provider
- If found: logs in existing user
- If not found: creates new User and Identity
- Generates JWT token
- Returns user and token
Google OAuth users can sign up without purchasing first. Adjust this logic if you want to require payment before signup.
JWT Token Structure
Tokens contain:
{
"id": "user-id",
"name": "John Doe",
"email": "john@example.com",
"plan": "pro",
"iat": 1234567890,
"exp": 1234571490
}iat- Issued at timestampexp- Expiration timestamp
Client-Side Usage
Storing the Token
// Store in cookie (recommended)
document.cookie = `token=${accessToken}; path=/; secure; httpOnly`
// Or localStorage
localStorage.setItem('token', accessToken)Making Authenticated Requests
const response = await fetch('http://localhost:5001/api/user', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})Security Considerations
Password Security
- Minimum 8 characters (enforce on client)
- Hashed with pbkdf2
- 100,000 iterations
- Unique salt per user
- SHA-512 algorithm
Token Security
- Tokens expire (default: 1 hour)
- Signed with JWT_SECRET
- Verified on every request
- Store in HTTP-only cookies when possible
Rate Limiting
Consider adding rate limiting to prevent brute force:
// Max 5 login attempts per 15 minutes per IP
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5
})
app.post('/api/user/auth', loginLimiter, userAuth)Next Steps
- Models - Understand data structure
- Identity Model - Authentication providers
- Billing Model - Payment integration
Last updated on