Skip to Content
ServerRoutesUser Authentication

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 match
  • 403 - No purchase found for email
  • 409 - User already exists

Flow

  1. Validates all fields are present
  2. Checks passwords match
  3. Looks for existing user with email
  4. Verifies billing record exists (from Stripe purchase)
  5. Hashes password with pbkdf2 (100,000 iterations)
  6. Creates User document
  7. Creates Identity document (local provider)
  8. Links Billing to User
  9. Generates JWT token
  10. 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 password
  • 401 - Invalid credentials
  • 404 - User not found

Flow

  1. Finds Identity by email and LOCAL provider
  2. Verifies password hash matches
  3. Loads User document
  4. Loads Billing to get current plan
  5. Generates JWT token
  6. Returns user and token

Password Verification

Passwords are verified using the Node.js crypto library. The system:

  1. Decodes the user’s unique salt
  2. Hashes the input password using pbkdf2 with the salt
  3. 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

  1. Verifies Google token with Google API
  2. Extracts user info (email, name, picture)
  3. Looks for existing Identity with Google provider
  4. If found: logs in existing user
  5. If not found: creates new User and Identity
  6. Generates JWT token
  7. 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 timestamp
  • exp - 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

Last updated on