Security Requirements & Patterns¶
MANDATORY Security Principles¶
- Security First: All content access is controlled by published status at the course level.
- Defense in Depth: Multiple layers of security validation.
- Least Privilege: Users only have access to what they need.
- Input Validation: All user inputs must be validated and sanitized.
Published Content Filtering¶
Static Generation Security¶
- generateStaticParams: MUST only include published courses.
- Entry Visibility: All entries within a published course are visible. Content visibility is controlled at the course level, not at the entry level.
- Runtime Verification: Always verify course published status before rendering content.
- Access Control: Implement proper access control in both static and dynamic routes.
Authentication & Authorization¶
Authentication Requirements¶
- Protected Routes: All LMS routes require an authenticated Auth.js (NextAuth) session.
- Token Validation: Verify JWT tokens on all API routes.
- Session Management: Handle session expiration gracefully.
- Re-authentication: Provide re-authentication flow when needed.
Password Policy (Canonical)¶
- New-password surfaces (sign-up, reset, invitation accept, admin user create, account password set/change) must enforce:
- minimum length: 8
- maximum length: 128
- at least one lowercase letter (
[a-z]) - at least one uppercase letter (
[A-Z]) - at least one digit (
[0-9])
- Special characters are allowed but not required.
- Use one shared schema in
@open-learning-hub/auth-utils/password-policy(newPasswordSchema) and import it in both apps. - Sign-in validation remains
min(1)+ bcrypt verification; do not re-evaluate password complexity at login. - Dev/test seed credentials must use a policy-compliant password via
SEED_DEMO_PASSWORD(and optionalSEED_SUPERADMIN_PASSWORDoverride) and stay synchronized with e2e fixtures through shared seed credential modules.
Role-Based Access Control¶
student: Access to enrolled courses only.course_admin: Access to dashboards for assigned courses only (percourse_admin_assignments); cannot edit course content.tenant_admin: Full management within their own tenant — users, courses, enrolments, course-admin assignments, tenant settings. Cannot cross tenants.super_admin: Global; can manage tenants, search across tenants viawithTenantOverride(), and impersonatetenant_admin(audit-logged).
See apps/lms/docs/reference/user-management.md for the full RBAC contract, permission matrix, and persona flows.
Input Validation & Sanitization¶
Client-Side Validation¶
- Validate all form inputs before submission.
- Provide immediate feedback on validation errors.
- Prevent invalid data from being submitted.
Server-Side Validation¶
- ALWAYS validate on server: Never trust client-side validation alone.
- Validate all API route inputs.
- Sanitize user inputs before processing.
- Use Zod or similar for schema validation.
Environment Variables Security¶
Public Variables¶
- Use
NEXT_PUBLIC_prefix only for truly public variables. - Never include sensitive data in public variables.
- Public variables are exposed in client-side bundle.
Private Variables¶
- Server-side variables MUST NOT use
NEXT_PUBLIC_prefix. - Store sensitive credentials in
.env.local(never commit). - Use secure key management in production.
- Rotate credentials regularly.
Variable Validation¶
// Example: Environment variable validation
function getRequiredEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
const authSecret = getRequiredEnv("AUTH_SECRET");
const databaseUrl = getRequiredEnv("DATABASE_URL");
API Route Security¶
Authentication Checks¶
- Verify authentication on all protected routes.
- Check user permissions before processing requests.
- Return appropriate status codes (401 Unauthorized, 403 Forbidden).
Rate Limiting¶
- Implement rate limiting on API routes and server actions that create accounts, authenticate users, mutate enrollments, submit assessments, or trigger cache invalidation.
- Prevent abuse and DoS attacks without exposing whether a specific account, course, or token exists.
- Return
429 Too Many Requestswith a user-safe error message and aRetry-Afterheader when possible. - Use Upstash Redis (
@upstash/ratelimit) in production. Use an in-memory fixed-window implementation for local development and tests; it must not add a production dependency or be trusted across multiple server instances. - Select the implementation through
RATE_LIMIT_DRIVER(upstashin production,memoryin dev/test). Production deployments usingupstashmust provideUPSTASH_REDIS_REST_URLandUPSTASH_REDIS_REST_TOKEN. - Never key limits by raw email alone. Hash or normalize identifiers before composing keys, and include the action name to avoid cross-endpoint interference.
| Endpoint | Limit | Window | Key scope |
|---|---|---|---|
POST /api/auth/callback/credentials |
10 requests | 15 min | IP + normalized email |
POST /learn/sign-up |
5 requests | 1 h | IP + normalized email |
POST .../enroll |
10 requests | 1 h | authenticated user |
POST .../submit-quiz |
30 requests | 1 h | authenticated user |
POST /api/cms/revalidate |
60 requests | 1 min | IP |
POST /api/csp-report |
60 requests | 1 min | IP |
The sign-up, enrollment, and quiz rows refer to route-owned server actions. Apply the same limits at the action boundary even when the action is invoked through React's server-action transport rather than a hand-authored /api/* route.
CORS Configuration¶
- Configure CORS properly for API routes.
- Only allow trusted origins.
- Use environment-specific CORS settings.
XSS Prevention¶
Content Sanitization¶
- Sanitize all user-generated content.
- Use React's built-in XSS protection.
- Escape HTML content when rendering.
- Use Content Security Policy (CSP) headers.
Portable Text Rendering¶
- Use
@portabletext/reactfor content. - Sanitize Portable Text
rawHtmlblocks at the server CMS-fetch boundary (apps/lms/src/lib/cms/sanitize-cms-html.ts) before rendering. - Validate and sanitize custom block types.
CSRF Protection¶
Token-Based Protection¶
- Use CSRF tokens for state-changing operations.
- Validate tokens on server-side.
- Implement SameSite cookie attributes.
Same-Origin Policy¶
- Leverage browser's same-origin policy.
- Use proper CORS configuration.
- Validate request origins.
Data Protection¶
Sensitive Data Handling¶
- Never log sensitive data (passwords, tokens, private keys).
- Encrypt sensitive data at rest.
- Use HTTPS for all data transmission.
- Implement proper data retention policies.
Database Authorization¶
- Enforce authorization in server components, server actions, and route handlers — there is no declarative database rule engine.
- Resolve the current session with
auth()fromsrc/auth.tsbefore any sensitive query. - Validate every input with a Zod schema at the boundary, then pass the parsed values to Kysely queries.
- Scope multi-tenant reads/writes by
tenantIdanduserIdcolumns explicitly in every Kysely query — never rely on the caller to filter. - Treat the database as the source of truth for roles; mirror them into the Auth.js session via the
jwt/sessioncallbacks but always re-check against the database for privileged actions. - Never rely solely on client-side validation.
Security Headers¶
Recommended Headers¶
// Example: Security headers in Next.js
export const securityHeaders = [
{
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: "Content-Security-Policy",
value:
"default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
},
];
Security Best Practices¶
- Regular Updates: Keep dependencies up-to-date.
- Security Audits: Regular security audits and penetration testing.
- Error Handling: Never expose sensitive information in error messages.
- Logging: Log security events (failed auth attempts, permission denials).
- Monitoring: Monitor for suspicious activity.
- Documentation: Document security requirements and patterns.