Security Architecture¶
Last Updated: 2025-11-22
Overview¶
The AccessALI Customer Portal implements multiple layers of security to protect user data, prevent unauthorized access, and maintain compliance with security best practices. This document outlines the security architecture, authentication mechanisms, and security controls implemented in the application.
Security First
Security is a critical aspect of the AccessALI portal. All developers must follow these security guidelines when implementing features.
Security Layers¶
graph TB
subgraph "Network Security"
HTTPS[HTTPS/TLS 1.3]
CDN[Vercel Edge Network]
WAF[Web Application Firewall]
end
subgraph "Application Security"
Auth[NextAuth.js Authentication]
Middleware[Auth Middleware]
RBAC[Role-Based Access Control]
RateLimit[Rate Limiting]
CSRF[CSRF Protection]
end
subgraph "Input Security"
Validation[Zod Input Validation]
Sanitization[Data Sanitization]
XSS[XSS Prevention]
end
subgraph "Data Security"
Encryption[Data Encryption]
Hashing[Password Hashing]
SQLInjection[SQL Injection Prevention]
end
HTTPS --> Auth
CDN --> Middleware
WAF --> Auth
Auth --> RBAC
Middleware --> RateLimit
Middleware --> CSRF
RBAC --> Validation
RateLimit --> Sanitization
CSRF --> XSS
Validation --> Encryption
Sanitization --> Hashing
XSS --> SQLInjection
Authentication & Authorization¶
NextAuth.js v5 Implementation¶
AccessALI uses NextAuth.js v5 (beta) for robust authentication and session management.
Supported Authentication Methods¶
| Method | Provider | Status | Use Case |
|---|---|---|---|
| Email/Password | Credentials Provider | ✅ Implemented | Standard login |
| Google OAuth | Google Provider | ✅ Implemented | Social login |
| Facebook OAuth | Facebook Provider | ✅ Implemented | Social login |
| Magic Link | Email Provider | 🔄 Planned | Passwordless login |
Authentication Flow¶
sequenceDiagram
participant User
participant NextAuth
participant Database
participant OAuth
alt Email/Password Login
User->>NextAuth: Submit credentials
NextAuth->>Database: Verify user
Database-->>NextAuth: User data
NextAuth->>NextAuth: Verify password (bcrypt)
NextAuth-->>User: Session token
else OAuth Login
User->>NextAuth: Click "Sign in with Google"
NextAuth->>OAuth: Redirect to OAuth provider
OAuth-->>User: OAuth consent screen
User->>OAuth: Grant permissions
OAuth-->>NextAuth: OAuth tokens
NextAuth->>Database: Create/update user & account
NextAuth-->>User: Session token
end
User->>NextAuth: Access protected page
NextAuth->>Database: Verify session
Database-->>NextAuth: Session valid
NextAuth-->>User: Authorized access
Role-Based Access Control (RBAC)¶
AccessALI implements a role-based permission system:
enum UserRole {
BUYER // Default role for property buyers
ADMIN // Full system access
SUPPORT // Customer support access
}
Permission Matrix¶
| Resource | BUYER | SUPPORT | ADMIN |
|---|---|---|---|
| View own properties | ✅ | ❌ | ✅ |
| View all properties | ❌ | ✅ | ✅ |
| Make payments | ✅ | ❌ | ✅ |
| Upload documents | ✅ | ✅ | ✅ |
| View all users | ❌ | ✅ | ✅ |
| Manage users | ❌ | ❌ | ✅ |
| System settings | ❌ | ❌ | ✅ |
Implementing Authorization¶
Server Actions:
'use server'
import { auth } from '@/lib/auth'
import { UnauthorizedError } from '@/lib/repositories/errors'
export async function adminOnlyAction() {
const session = await auth()
// Check authentication
if (!session?.user?.id) {
throw new UnauthorizedError('Must be logged in')
}
// Check authorization
if (session.user.role !== 'ADMIN') {
throw new UnauthorizedError('Admin access required')
}
// Perform admin action
return doAdminStuff()
}
Middleware Protection:
// middleware.ts
import { auth } from '@/lib/auth'
import { NextResponse } from 'next/server'
export default auth((req) => {
const { pathname } = req.nextUrl
// Protect admin routes
if (pathname.startsWith('/admin')) {
if (req.auth?.user?.role !== 'ADMIN') {
return NextResponse.redirect(new URL('/unauthorized', req.url))
}
}
// Protect all dashboard routes
if (pathname.startsWith('/dashboard')) {
if (!req.auth) {
return NextResponse.redirect(new URL('/login', req.url))
}
}
return NextResponse.next()
})
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
Session Management¶
Session Strategy:
- Type: JWT (JSON Web Tokens)
- Storage: HTTP-only cookies (client-side), Database (server-side)
- Expiration: 30 days (configurable)
- Refresh: Automatic on activity
Session Security:
// JWT Configuration
{
secret: process.env.NEXTAUTH_SECRET, // Strong secret (min 32 chars)
maxAge: 30 * 24 * 60 * 60, // 30 days
updateAge: 24 * 60 * 60, // Refresh daily
encryption: true, // Encrypted JWTs
sameSite: 'lax', // CSRF protection
httpOnly: true, // No JavaScript access
secure: process.env.NODE_ENV === 'production' // HTTPS only in prod
}
Session Best Practices
- Always use HTTPS in production
- Set appropriate session expiration
- Implement logout on all devices
- Monitor for suspicious session activity
Input Validation & Sanitization¶
Zod Schema Validation¶
All user inputs are validated using Zod schemas before processing.
import { z } from 'zod'
// Define schema
export const createPaymentSchema = z.object({
propertyId: z.string().cuid(),
amount: z.number().positive().max(100000000),
paymentMethod: z.enum(['CREDIT_CARD', 'BANK_TRANSFER', 'CASH']),
notes: z.string().max(500).optional()
})
// Validate in Server Action
export async function createPayment(data: unknown) {
// Validate input
const validated = createPaymentSchema.parse(data) // Throws if invalid
// Safe to use validated data
return await paymentRepository.create(validated)
}
XSS Prevention¶
React Automatic Escaping:
React automatically escapes all values rendered in JSX, preventing XSS attacks.
// ✅ Safe: React escapes user input
<div>{user.name}</div>
// ❌ Dangerous: Bypasses React escaping
<div dangerouslySetInnerHTML={{ __html: user.bio }} />
Content Security Policy (CSP):
// next.config.mjs
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
export default {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}
SQL Injection Prevention¶
Prisma ORM Protection:
Prisma uses parameterized queries, preventing SQL injection:
// ✅ Safe: Prisma parameterizes inputs
await prisma.user.findMany({
where: {
email: userInput // Automatically sanitized
}
})
// ❌ Never use raw SQL with user input
await prisma.$executeRaw`SELECT * FROM users WHERE email = ${userInput}`
// ✅ If raw SQL needed, use parameterized queries
await prisma.$executeRaw`SELECT * FROM users WHERE email = ${Prisma.sql`${userInput}`}`
CSRF Protection¶
Next.js Built-in CSRF Protection¶
Next.js Server Actions include automatic CSRF protection:
// Form with CSRF token
<form action={serverAction}>
<input type="text" name="data" />
<button type="submit">Submit</button>
{/* CSRF token automatically included */}
</form>
API Routes CSRF Protection¶
// API route with CSRF check
import { NextRequest } from 'next/server'
export async function POST(req: NextRequest) {
// Verify origin header
const origin = req.headers.get('origin')
const host = req.headers.get('host')
if (origin !== `https://${host}`) {
return new Response('Invalid origin', { status: 403 })
}
// Process request
return Response.json({ success: true })
}
Rate Limiting¶
Redis-Based Rate Limiting¶
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
// Create rate limiter
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
analytics: true,
})
// Apply in middleware
export async function middleware(request: NextRequest) {
const ip = request.ip ?? '127.0.0.1'
const { success, pending, limit, reset, remaining } =
await ratelimit.limit(ip)
if (!success) {
return new Response('Too Many Requests', {
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
},
})
}
return NextResponse.next()
}
Rate Limit Tiers¶
| Endpoint | Limit | Window | Use Case |
|---|---|---|---|
/api/auth/* |
5 requests | 15 minutes | Login attempts |
/api/upload |
10 requests | 1 hour | File uploads |
/api/* (general) |
100 requests | 15 minutes | API calls |
| Server Actions | 60 requests | 1 minute | Form submissions |
Password Security¶
Password Hashing (bcrypt)¶
import bcrypt from 'bcryptjs'
// Hash password on registration
export async function hashPassword(password: string): Promise<string> {
const saltRounds = 12 // Cost factor
return bcrypt.hash(password, saltRounds)
}
// Verify password on login
export async function verifyPassword(
password: string,
hashedPassword: string
): Promise<boolean> {
return bcrypt.compare(password, hashedPassword)
}
Password Requirements¶
const passwordSchema = z.string()
.min(8, 'Password must be at least 8 characters')
.max(100, 'Password must be less than 100 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number')
.regex(/[^A-Za-z0-9]/, 'Password must contain at least one special character')
Never Log Passwords
Never log passwords or password hashes. Use [REDACTED] in logs.
Data Encryption¶
At-Rest Encryption¶
- Database: PostgreSQL with encryption at rest (Neon)
- Backups: Encrypted backups
- File Storage: Vercel Blob Storage (encrypted by default)
In-Transit Encryption¶
- HTTPS/TLS 1.3: All production traffic
- Certificate: Automatic SSL via Vercel
- HSTS: HTTP Strict Transport Security enabled
// Security headers
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
}
Secure Headers¶
Recommended Security Headers¶
// next.config.mjs
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
}
]
}
]
}
Error Handling Security¶
Secure Error Messages¶
// ❌ Bad: Exposes internal details
throw new Error(`Database connection failed: ${dbError.message}`)
// ✅ Good: Generic message to client, detailed logging
export class DatabaseError extends Error {
constructor(operation: string, cause?: unknown) {
super(`Database operation failed: ${operation}`)
this.name = 'DatabaseError'
this.cause = cause
}
}
// Log detailed error server-side
console.error('Database error:', { operation, cause: dbError })
// Return generic error to client
return { error: 'An error occurred. Please try again.' }
Never Expose Stack Traces
Never return stack traces or internal error details to clients in production. Log them server-side only.
Secrets Management¶
Environment Variables¶
# ✅ Store in .env.local (never committed)
DATABASE_URL="postgresql://..."
NEXTAUTH_SECRET="strong-random-secret"
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."
# ❌ Never hardcode secrets
const apiKey = "sk-1234567890" // Don't do this!
Production Secrets¶
- Store in Vercel Environment Variables
- Rotate secrets regularly
- Use different secrets per environment
- Never log secret values
Security Monitoring¶
Logging¶
import winston from 'winston'
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
})
// Log security events
logger.warn('Failed login attempt', {
email: '[REDACTED]',
ip: request.ip,
timestamp: new Date().toISOString()
})
Security Events to Monitor¶
- Failed login attempts (> 5 in 15 min)
- Password reset requests
- Role changes
- Unusual access patterns
- Rate limit violations
- Invalid tokens
Security Checklist¶
Development¶
- All inputs validated with Zod
- SQL injection prevented (Prisma ORM)
- XSS prevented (React escaping)
- CSRF protection enabled
- Authentication required for protected routes
- Authorization checks in place
- Passwords hashed with bcrypt
- Secrets in environment variables
- Security headers configured
- Rate limiting implemented
Pre-Production¶
- Security audit completed
- Dependency vulnerabilities fixed
- HTTPS enforced
- CSP headers configured
- Error messages sanitized
- Logging configured
- Secrets rotated
- Backup strategy in place
Production¶
- HTTPS/TLS enabled
- Security monitoring active
- Regular security updates
- Incident response plan
- Regular backups
- Access logs reviewed
Common Vulnerabilities & Prevention¶
OWASP Top 10¶
| Vulnerability | Prevention | Implementation |
|---|---|---|
| Injection | Parameterized queries | Prisma ORM |
| Broken Authentication | Strong session management | NextAuth.js |
| Sensitive Data Exposure | Encryption & secure storage | TLS, env vars |
| XML External Entities (XXE) | Disable XML parsing | N/A (no XML) |
| Broken Access Control | RBAC & authorization checks | Middleware |
| Security Misconfiguration | Secure headers | next.config.mjs |
| XSS | Input validation & escaping | React, Zod |
| Insecure Deserialization | Validate deserialized data | Zod schemas |
| Using Components with Known Vulnerabilities | Regular updates | Dependabot |
| Insufficient Logging & Monitoring | Comprehensive logging | Winston |
Related Documentation¶
- System Architecture - Overall architecture
- Authentication - Authentication implementation
- Error Handling - Error handling patterns
Next Steps¶
- Review Authentication API for implementation details
- Check Error Handling Guide for error patterns
- Explore System Architecture for security layers