How the FL Auth Lambda system works.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client Application β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β HTTP Requests
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AWS API Gateway β
β (Routes to /auth/* endpoints) β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β Lambda Invoke
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AWS Lambda (Node.js 18 runtime) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Express.js Application β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Middleware Chain β β β
β β β β’ CORS Handling β β β
β β β β’ Request Logger (Request ID tracking) β β β
β β β β’ Body Parser (JSON validation with Zod) β β β
β β β β’ Error Handler (Consistent error responses) β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Route Handler (7 endpoints) β β β
β β β β’ POST /auth/signup (Create account) β β β
β β β β’ POST /auth/login (Authenticate) β β β
β β β β’ POST /auth/verify (Verify token) β β β
β β β β’ POST /auth/refresh-token (New access token) β β β
β β β β’ POST /auth/setup-password (First-time setup) β β β
β β β β’ POST /auth/reset-password (Change password) β β β
β β β β’ DELETE /auth/account (Delete account) β β β
β β ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ β β
β β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Utility Functions β β β
β β β β’ Password: Hash (bcrypt + pepper) β β β
β β β β’ JWT: Sign & Verify tokens β β β
β β β β’ Secrets: Load from AWS Secrets Manager β β β
β β β β’ Validation: Zod schema checking β β β
β β ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββΌβββββββββββββββ β
β β β β β
βββββββββββ¬βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββββ
β β β
βββββββββββββββ ββββββββββββββββ βββββββββββββββββββββββ
β Neo4j DB β β AWS Secretsβ β FLC Notify Service β
β β β Manager β β (Email Lambda) β
β β’ Member β β β β β
β :User β β β’ JWT Secret β β β’ Welcome email β
β nodes β β β’ Pepper β β β’ Reset email β
β β’ Passwords β β β’ DB creds β β β’ Delete email β
β β’ Tokens β β β’ API keys β βββββββββββββββββββββββ
βββββββββββββββ ββββββββββββββββ
| Component | Technology | Version |
|---|---|---|
| Runtime | Node.js | 18+ |
| Framework | Express.js | 4.18+ |
| Language | TypeScript | 5.3+ |
| Database | Neo4j | 4.4+ |
| Deployment | AWS Lambda | - |
| API Gateway | AWS API Gateway | - |
| Secrets | AWS Secrets Manager | - |
| Testing | Jest | 30.2+ |
| Validation | Zod | - |
| Password Hashing | bcrypt | 5.1+ |
| Auth | JWT (jsonwebtoken) | 9.0+ |
1. Client β POST /auth/signup { email, password, firstName }
2. Middleware β Validate with Zod schema
3. Handler β Hash password with bcrypt + pepper
4. Handler β Create Member:User node in Neo4j
5. Handler β Async email (fire-and-forget)
6. Response β 201 with tokens and user object
7. Email Service β Sends welcome email (async)
1. Client β POST /auth/login { email, password }
2. Middleware β Validate email/password
3. Handler β Query User node by email
4. Handler β Compare password (bcrypt)
5. Handler β Check if password is NULL (new users)
β
ββ If NULL: Return 401 with requiresPasswordSetup flag
ββ If valid: Generate tokens, return 200
6. Response β { accessToken, refreshToken, user }
1. Client β POST /auth/refresh-token { refreshToken }
2. Handler β Verify JWT signature (async - loads secret)
3. Handler β Query User node to confirm exists
4. Handler β Generate new access token
5. Response β { accessToken, refreshToken, user }
1. Client β POST /auth/setup-password { email, token, password }
2. Handler β Verify setup token (JWT verification)
3. Handler β Query User by email with token
4. Handler β Check password is still NULL
5. Handler β Hash new password with bcrypt + pepper
6. Handler β Update User node with password
7. Response β 200 success
Every user node in Neo4j has both labels:
(:Member:User {
id: "uuid-string",
email: "user@example.com",
password: "bcrypt-hashed-password",
firstName: "John",
lastName: "Doe",
createdAt: datetime,
updatedAt: datetime,
lastLoginAt: datetime
})Why?
- β
Backward compatibility (old code still uses
:Member) - β
Cleaner new code (auth routes use
:User) - β Simpler than having separate nodes
- β Migration was "add label" not "recreate nodes"
Password Hashing:
plaintext password
β
bcrypt hash (10 rounds)
β
concat with PEPPER
β
stored in database
NULL Passwords:
- New users created via signup: password = actual hash
- Users created by admin or migrated: password = NULL
- Login checks:
if (user.password === null)β returns 401 withrequiresPasswordSetup: true
Access Token (30 minutes)
{
"userId": "user-id-here",
"email": "user@example.com",
"type": "access",
"iat": 1234567890,
"exp": 1234569690 // 30 min from now
}
Refresh Token (7 days)
{
"userId": "user-id-here",
"type": "refresh",
"iat": 1234567890,
"exp": 1235000000 // 7 days from now
}
Secrets are loaded from AWS Secrets Manager, not stored as environment variables:
// Automatic secret selection based on Lambda function name
const functionName = context.functionName
const secretName = functionName.includes('dev')
? 'dev-fl-auth-service-secrets'
: 'fl-auth-service-secrets'
// Loaded on first request, cached for reuse
const jwt_secret = await getSecret('JWT_SECRET')
const neo4j_uri = await getSecret('NEO4J_URI')All errors follow consistent format:
{
"error": "User not found",
"statusCode": 404,
"requestId": "req-12345-abc",
"requiresPasswordSetup": false // (optional)
}Exception thrown in handler
β
Express error middleware catches it
β
Create ApiError (status, message, optional data)
β
Format consistent JSON response
β
Send to client
β
Log to CloudWatch with request ID
Requests flow through middleware in order:
1. CORS Handling
ββ Set CORS headers
2. Body Parser
ββ Parse JSON, validate with Zod
3. Request Logger
ββ Generate request ID, log entry
4. Route Handler
ββ Execute business logic
5. Error Handler
ββ Catch and format errors
// Unique email constraint
CREATE CONSTRAINT user_email_unique IF NOT EXISTS
FOR (u:User) REQUIRE u.email IS UNIQUE
// Indexes for fast lookup
CREATE INDEX user_id IF NOT EXISTS
FOR (u:User) ON (u.id)
CREATE INDEX user_email IF NOT EXISTS
FOR (u:User) ON (u.email)
CREATE INDEX member_id IF NOT EXISTS
FOR (m:Member) ON (m.id)Find user by email:
MATCH (u:User {email: $email})
RETURN uUpdate password:
MATCH (u:User {id: $userId})
SET u.password = $hashedPassword, u.updatedAt = datetime()
RETURN uDelete account:
MATCH (u:User {id: $userId})
DETACH DELETE uUser submits password
β
Server receives request (HTTPS only)
β
Validate input (Zod schema)
β
Query database for user
β
bcrypt compare password
β
Generate JWT tokens
β
Return tokens (client stores in secure storage)
β
Client sends access token on each request
β
Server verifies token signature
β
Token expired? β Use refresh token to get new one
β Password Security:
- bcrypt hashing (not reversible)
- Pepper added (additional salt)
- 10 salt rounds (slow to crack)
- NULL password for new users (forces setup before login)
β Token Security:
- JWT signed with secret
- 30-min access token (expiration)
- 7-day refresh token (longer expiration)
- Signature verified on each request
β Input Validation:
- All inputs validated with Zod
- Email format checked
- Password strength enforced
- Request size limits
β Secrets Security:
- All secrets in AWS Secrets Manager (not environment variables)
- Never logged or exposed in errors
- Rotatable without code changes
- IAM-protected access
// Neo4j driver configuration
{
maxConnectionPoolSize: 50, // Max 50 concurrent connections
connectionAcquisitionTimeout: 10000, // Wait 10s for connection
}- Concurrent Executions: Auto-scales with traffic
- Connection Pool: Shared across all Lambda instances
- Cold Starts: ~1-2 seconds (loading secrets + DB connect)
- Warm Starts: ~100-300ms (reusing container)
- Average request: 100-200ms
- Peak latency: 500-1000ms (cold start)
- Database queries: 20-50ms typically
- Email sending: Async (doesn't block response)
- Error Rate: Track 401/400/500 errors
- Request Latency: Average response time
- Database Connections: Active Neo4j connections
- Token Errors: Invalid/expired token count
- Email Failures: Failed email sends (logged but non-blocking)
# Production logs
aws logs tail /aws/lambda/fl-auth-service-lambda --follow
# Watch for these messages:
# β
Successfully loaded secrets
# β
Successfully connected to Neo4j
# β Failed to load secrets
# β Failed to connect to Neo4j
# β Database query failedSee Deployment Guide for details.
Summary:
- Code pushed to
mainβ deploys to production Lambda - Code pushed to
devβ deploys to dev Lambda - GitHub Actions handles build, test, deploy
- Secrets automatically loaded from Secrets Manager
- API Endpoints - Understand each endpoint
- Getting Started - Local development
- Deployment Guide - Deploy to AWS
- Notifications - Email integration
See Also: