Cours Next.js · Module 7: Authentification et autorisation
Sécurité de l’application et OWASP
Dans cette leçon5
In previous modules, we focused on implementing authentication, session management, the role and permission system, route protection middleware, and the Context Provider for authentication. Now we will shift our attention to the broader area of web application security, with particular focus on OWASP (Open Web Application Security Project) guidelines.
OWASP is a non-profit organization that works to improve software security. It regularly publishes the Top 10 list of the most serious security threats for web applications, which is a fundamental reference point for developers striving to create secure applications.
OWASP Top 10 et leur application dans les applications Next.js
We will look at how to secure Next.js applications against the most serious threats from the OWASP Top 10 list:
1. Broken Access Control
Threat: Improper implementation of access control, allowing unauthorized users to access protected resources or perform unauthorized operations.
Zabezpieczenia w Next.js:
1// 1. Middleware do ochrony tras
2// middleware.ts
3import { NextResponse } from 'next/server';
4import type { NextRequest } from 'next/server';
5import { getToken } from 'next-auth/jwt';
6
7export async function middleware(request: NextRequest) {
8 const token = await getToken({ req: request });
9
10 // Check user permissions
11 if (!token) {
12 return NextResponse.redirect(new URL('/auth/login', request.url));
13 }
14
15 // Role-based access control
16 if (request.nextUrl.pathname.startsWith('/admin') && token.role !== 'admin') {
17 return NextResponse.redirect(new URL('/unauthorized', request.url));
18 }
19
20 return NextResponse.next();
21}
22
23// 2. Weryfikacja permissions w Route Handlers
24// app/api/protected/resource/route.ts
25import { NextResponse } from 'next/server';
26import { getServerSession } from 'next-auth/next';
27import { authOptions } from '@/app/api/auth/[...nextauth]/route';
28
29export async function GET() {
30 const session = await getServerSession(authOptions);
31
32 if (!session) {
33 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
34 }
35
36 // Vérification des permissions pour une ressource spécifique
37 const hasPermission = await checkResourcePermission(session.user.id, 'resource-id');
38
39 if (!hasPermission) {
40 return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
41 }
42
43 // Continue with resource access...
44}
45
46// Function that checks permissions to a resource
47async function checkResourcePermission(userId: string, resourceId: string) {
48 // Implémentation de la vérification des permissions, p.ex. via une requête à la base de données
49 // It's worth implementing a caching mechanism for frequently checked permissions
50 return true; // Sample implementation
51}Dodatkowe najlepsze praktyki:
- Implement access control at the server level, never rely solely on client-side security
- Apply the default deny principle (deny by default)
- Regularly review and test access control mechanisms
- Implement an account lockout mechanism after multiple failed login attempts
2. Cryptographic Failures
Threat: Improper or insufficient application of cryptography, leading to the disclosure of sensitive data.
Zabezpieczenia w Next.js:
1// 1. Secure password storage
2// lib/auth.ts
3import { hash, compare } from 'bcrypt';
4
5// Hashing password during registration
6export async function hashPassword(password: string): Promise<string> {
7 // We use 12 rounds for a good compromise between security and performance
8 return hash(password, 12);
9}
10
11// Verifying password during login
12export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
13 return compare(password, hashedPassword);
14}
15
16// 2. Encrypting sensitive data in the database
17// lib/encryption.ts
18import crypto from 'crypto';
19
20const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY as string; // Must be 32 bytes (256 bits)
21const IV_LENGTH = 16; // For AES, initialization vector is 16 bytes
22
23export function encrypt(text: string): string {
24 const iv = crypto.randomBytes(IV_LENGTH);
25 const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv);
26 let encrypted = cipher.update(text, 'utf8', 'hex');
27 encrypted += cipher.final('hex');
28 return `${iv.toString('hex')}:${encrypted}`;
29}
30
31export function decrypt(text: string): string {
32 const [ivHex, encryptedHex] = text.split(':');
33 const iv = Buffer.from(ivHex, 'hex');
34 const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv);
35 let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
36 decrypted += decipher.final('utf8');
37 return decrypted;
38}Dodatkowe najlepsze praktyki:
- Always use HTTPS for all application pages
- Implement appropriate security-related HTTP headers (HSTS, CSP)
- Mets régulièrement à jour les bibliothèques cryptographiques
- Implement a cryptographic key rotation strategy
- Do not store sensitive data in local browser storages (localStorage, sessionStorage)
3. Injection (Wstrzykiwanie)
Threat: Injection of untrusted data into interpreters as part of a command or query, which can lead to execution of unauthorized commands or data access.
Zabezpieczenia w Next.js:
1// 1. Requêtes sécurisées vers la base de données (utilisation d’un ORM)
2// app/api/users/route.ts
3import { NextResponse } from 'next/server';
4import { prisma } from '@/lib/prisma';
5
6export async function GET(request: Request) {
7 const { searchParams } = new URL(request.url);
8 const name = searchParams.get('name');
9
10 // Requête sécurisée avec un ORM (Prisma)
11 // Instead of direct injection of parameters into an SQL query
12 const users = await prisma.user.findMany({
13 where: {
14 name: {
15 contains: name || '',
16 },
17 },
18 select: {
19 id: true,
20 name: true,
21 email: true,
22 },
23 });
24
25 return NextResponse.json(users);
26}
27
28// 2. Input data validation using the zod library
29// app/api/products/route.ts
30import { NextResponse } from 'next/server';
31import { z } from 'zod';
32
33// Validation schema
34const ProductSchema = z.object({
35 name: z.string().min(1).max(100),
36 price: z.number().positive(),
37 description: z.string().optional(),
38 categoryId: z.string().uuid(),
39});
40
41export async function POST(request: Request) {
42 try {
43 const body = await request.json();
44
45 // Validate input data
46 const result = ProductSchema.safeParse(body);
47
48 if (!result.success) {
49 return NextResponse.json(
50 { error: 'Invalid input', details: result.error.format() },
51 { status: 400 }
52 );
53 }
54
55 // Traitement ultérieur des données sécurisées et validées...
56
57 } catch (error) {
58 return NextResponse.json(
59 { error: 'Server error' },
60 { status: 500 }
61 );
62 }
63}Dodatkowe najlepsze praktyki:
- Filter and sanitize input and output data
- Use complex, randomized passwords and session identifiers
- Implement session timeout and limit login attempts
- Échappe toutes les données avant de les placer dans des requêtes SQL, des scripts ou du HTML
- Avoid dynamic generation of JavaScript code by string concatenation
4. Insecure Design (Niebezpieczny projekt)
Threat: Lack of appropriate security mechanisms at the application design level, leading to fundamental security vulnerabilities.
Zabezpieczenia w Next.js:
1// 1. Password security policy implementation
2// lib/passwordPolicy.ts
3export function isPasswordStrong(password: string): { isValid: boolean; reason?: string } {
4 // Minimum length of 8 characters
5 if (password.length < 8) {
6 return { isValid: false, reason: 'Password must be at least 8 characters' };
7 }
8
9 // Wymaga wielkich liter
10 if (!/[A-Z]/.test(password)) {
11 return { isValid: false, reason: 'Password must contain at least one uppercase letter' };
12 }
13
14 // Requires lowercase letters
15 if (!/[a-z]/.test(password)) {
16 return { isValid: false, reason: 'Password must contain at least one lowercase letter' };
17 }
18
19 // Wymaga cyfr
20 if (!/[0-9]/.test(password)) {
21 return { isValid: false, reason: 'Password must contain at least one digit' };
22 }
23
24 // Requires special characters
25 if (!/[^A-Za-z0-9]/.test(password)) {
26 return { isValid: false, reason: 'Password must contain at least one special character' };
27 }
28
29 // Check if the password is not on the list of common, easily guessable passwords
30 const commonPasswords = ['Password123!', 'Admin123!', '12345678Aa!', 'Qwerty123!']; // In practice, this should be a larger database
31 if (commonPasswords.includes(password)) {
32 return { isValid: false, reason: 'Password is too common and easy to guess' };
33 }
34
35 return { isValid: true };
36}
37
38// 2. Implémentation d’un système de permissions basé sur le modèle de données
39// lib/permissions.ts
40import { prisma } from '@/lib/prisma';
41
42// Model allowing certain operations only to the resource owner
43export async function canModifyResource(userId: string, resourceId: string): Promise<boolean> {
44 const resource = await prisma.resource.findUnique({
45 where: { id: resourceId },
46 select: { userId: true },
47 });
48
49 // Resource does not exist or the user is not the owner
50 if (!resource || resource.userId !== userId) {
51 return false;
52 }
53
54 return true;
55}Dodatkowe najlepsze praktyki:
- Implementuj mechanizm wielopoziomowej ochrony (defense in depth)
- Perform threat modeling at an early stage of application development
- Design with the principle of least privilege in mind
- Traite les données utilisateur comme non fiables et valide-les toujours
- Regularly review and update security mechanisms
5. Security Misconfiguration
Threat: Improper security configuration in any application component, which can lead to unauthorized access or data leakage.
Zabezpieczenia w Next.js:
1// 1. Configuration de la Content Security Policy
2// next.config.js
3const ContentSecurityPolicy = `
4 default-src 'self';
5 script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.example.com;
6 style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
7 font-src 'self' https://fonts.gstatic.com;
8 img-src 'self' data: https:;
9 connect-src 'self' https://api.example.com;
10 frame-src 'self';
11`;
12
13module.exports = {
14 async headers() {
15 return [
16 {
17 source: '/(.*)',
18 headers: [
19 {
20 key: 'Content-Security-Policy',
21 value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
22 },
23 {
24 key: 'X-Content-Type-Options',
25 value: 'nosniff',
26 },
27 {
28 key: 'X-Frame-Options',
29 value: 'DENY',
30 },
31 {
32 key: 'X-XSS-Protection',
33 value: '1; mode=block',
34 },
35 {
36 key: 'Referrer-Policy',
37 value: 'strict-origin-when-cross-origin',
38 },
39 {
40 key: 'Permissions-Policy',
41 value: 'camera=(), microphone=(), geolocation=(self)',
42 },
43 ],
44 },
45 ];
46 },
47};
48
49// 2. Secure use of environment variables
50// lib/config.ts
51interface Config {
52 database: {
53 url: string;
54 };
55 auth: {
56 secret: string;
57 tokenExpiration: number;
58 };
59 services: {
60 apiKey: string;
61 endpoint: string;
62 };
63}
64
65export const config: Config = {
66 database: {
67 url: process.env.DATABASE_URL || '',
68 },
69 auth: {
70 secret: process.env.AUTH_SECRET || '',
71 tokenExpiration: Number(process.env.TOKEN_EXPIRATION) || 3600,
72 },
73 services: {
74 apiKey: process.env.SERVICE_API_KEY || '',
75 endpoint: process.env.SERVICE_ENDPOINT || '',
76 },
77};
78
79// Fonction pour vérifier la configuration au démarrage de l'application
80export function validateConfig() {
81 const requiredEnvVars = [
82 'DATABASE_URL',
83 'AUTH_SECRET',
84 'SERVICE_API_KEY',
85 'SERVICE_ENDPOINT',
86 ];
87
88 const missingEnvVars = requiredEnvVars.filter(
89 (envVar) => !process.env[envVar]
90 );
91
92 if (missingEnvVars.length > 0) {
93 throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
94 }
95
96 if (process.env.AUTH_SECRET && process.env.AUTH_SECRET.length < 32) {
97 throw new Error('AUTH_SECRET should be at least 32 characters long for security');
98 }
99
100 console.log('Configuration validated successfully');
101}Dodatkowe najlepsze praktyki:
- Remove or disable unnecessary features, components, and dependencies
- Regularly update all application components and dependencies
- Implementuj mechanizm audytowania konfiguracji
- Never place sensitive data in source code or publicly accessible files
- Use different configurations for development and production environments
6. Vulnerable and Outdated Components
Threat: Using components and libraries containing known security vulnerabilities or that are no longer supported.
Zabezpieczenia w Next.js:
1// 1. Automatic dependency scanning
2// package.json
3{
4 "scripts": {
5 "audit": "npm audit",
6 "audit:fix": "npm audit fix",
7 "outdated": "npm outdated",
8 "update": "npm update",
9 "deps:check": "npx depcheck",
10 "prepare": "husky install"
11 },
12 "husky": {
13 "hooks": {
14 "pre-commit": "npm run audit"
15 }
16 }
17}
18
19// 2. Introduction de la politique SRI (Subresource Integrity)
20// For critical external JavaScript libraries
21// app/layout.tsx (example)
22import Script from 'next/script';
23
24export default function RootLayout({ children }: { children: React.ReactNode }) {
25 return (
26 <html lang="en">
27 <head>
28 {/* Using SRI to verify the integrity of an external script */}
29 <Script
30 src="https://cdn.example.com/library.min.js"
31 integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
32 crossOrigin="anonymous"
33 />
34 </head>
35 <body>{children}</body>
36 </html>
37 );
38}Dodatkowe najlepsze praktyki:
- Regularly running
npm auditor using tools like Snyk, Dependabot - Removing unused dependencies
- Subscribing to notifications about security vulnerabilities in used libraries
- Using trusted, well-maintained libraries with an active community
- Preparing procedures for quick component updates after vulnerability discovery
7. Identification and Authentication Failures
Threat: Improper implementation of authentication mechanisms, allowing attackers to access user accounts.
Zabezpieczenia w Next.js:
1// 1. Login attempt limiting implementation
2// lib/rateLimit.ts
3import { Redis } from '@upstash/redis';
4
5const redis = new Redis({
6 url: process.env.UPSTASH_REDIS_URL!,
7 token: process.env.UPSTASH_REDIS_TOKEN!,
8});
9
10export async function rateLimit(
11 identifier: string, // IP lub user identifier
12 limit: number, // Maximum number of attempts
13 window: number // Okno czasowe w sekundach
14): Promise<{ success: boolean; remaining: number; reset: number }> {
15 const now = Math.floor(Date.now() / 1000);
16 const key = `ratelimit:${identifier}`;
17
18 // Récupérer les données actuelles sur les limitations
19 const entry = await redis.hgetall(key);
20
21 if (!entry.count) {
22 // Pierwszy wpis
23 await redis.hset(key, {
24 count: 1,
25 reset: now + window,
26 });
27 await redis.expire(key, window);
28
29 return {
30 success: true,
31 remaining: limit - 1,
32 reset: now + window,
33 };
34 }
35
36 // Check if the time window has passed
37 if (parseInt(entry.reset) < now) {
38 // Resetuj licznik
39 await redis.hset(key, {
40 count: 1,
41 reset: now + window,
42 });
43 await redis.expire(key, window);
44
45 return {
46 success: true,
47 remaining: limit - 1,
48 reset: now + window,
49 };
50 }
51
52 // Check if the limit has been exceeded
53 const count = parseInt(entry.count);
54 if (count >= limit) {
55 return {
56 success: false,
57 remaining: 0,
58 reset: parseInt(entry.reset),
59 };
60 }
61
62 // Mettre à jour le compteur
63 await redis.hincrby(key, 'count', 1);
64
65 return {
66 success: true,
67 remaining: limit - count - 1,
68 reset: parseInt(entry.reset),
69 };
70}
71
72// 2. Login page implementation with attempt limiting
73// app/api/auth/login/route.ts
74import { NextResponse } from 'next/server';
75import { rateLimit } from '@/lib/rateLimit';
76
77export async function POST(request: Request) {
78 try {
79 const { email, password } = await request.json();
80
81 // Récupérer l’adresse IP de l’utilisateur
82 const ip = request.headers.get('x-forwarded-for') || 'unknown';
83
84 // Limit login attempts: 5 attempts within 15 minutes
85 const rateLimitResult = await rateLimit(ip, 5, 15 * 60);
86
87 if (!rateLimitResult.success) {
88 return NextResponse.json(
89 {
90 error: 'Too many login attempts. Please try again later.',
91 reset: rateLimitResult.reset,
92 },
93 { status: 429 }
94 );
95 }
96
97 // Kontynuuj proces logowania...
98
99 } catch (error) {
100 console.error('Login error:', error);
101 return NextResponse.json(
102 { error: 'An error occurred during login' },
103 { status: 500 }
104 );
105 }
106}Dodatkowe najlepsze praktyki:
- Implement strong password requirements
- Require multi-factor authentication (MFA) for administrator accounts and users with high permissions
- Do not reveal detailed information about authentication errors
- Implement secure password recovery mechanisms
- Use one-time token mechanisms for critical operations
8. Software and Data Integrity Failures
Threat: Lack of software and data integrity verification, allowing the introduction of incorrect or malicious components.
Zabezpieczenia w Next.js:
1// 1. Implementation of a digital signature mechanism for critical data
2// lib/signatures.ts
3import crypto from 'crypto';
4
5const SIGNATURE_KEY = process.env.SIGNATURE_KEY!;
6
7// Génération de la signature pour les données
8export function signData(data: any): string {
9 const dataString = typeof data === 'string' ? data : JSON.stringify(data);
10 return crypto
11 .createHmac('sha256', SIGNATURE_KEY)
12 .update(dataString)
13 .digest('hex');
14}
15
16// Vérification de la signature des données
17export function verifySignature(data: any, signature: string): boolean {
18 const expectedSignature = signData(data);
19 return crypto.timingSafeEqual(
20 Buffer.from(expectedSignature, 'hex'),
21 Buffer.from(signature, 'hex')
22 );
23}
24
25// 2. Implementation of webhook verification for external services
26// app/api/webhooks/payment-processor/route.ts
27import { NextResponse } from 'next/server';
28import { verifySignature } from '@/lib/signatures';
29
30export async function POST(request: Request) {
31 const body = await request.text();
32 const signature = request.headers.get('X-Webhook-Signature') || '';
33
34 // Weryfikacja podpisu webhook
35 if (!verifySignature(body, signature)) {
36 console.error('Invalid webhook signature');
37 return NextResponse.json(
38 { error: 'Invalid signature' },
39 { status: 401 }
40 );
41 }
42
43 // Traitement du webhook vérifié...
44 const data = JSON.parse(body);
45
46 //
47 return NextResponse.json({ status: 'success' });
48}Dodatkowe najlepsze praktyki:
- Verify the integrity of files and source code in the CI/CD process
- Verify the origin of external libraries and components
- Use SRI (Subresource Integrity) mechanisms for external resources
- Implement automated security tests in the CI/CD process
- Surveille et analyse les changements dans les fichiers de configuration et le code de l'application
9. Security Logging and Monitoring Failures
Threat: Insufficient logging, monitoring, and alerting of security events, which prevents detection, escalation, and response to incidents.
Zabezpieczenia w Next.js:
1// 1. Implementacja rozbudowanego logowania
2// lib/logger.ts
3import winston from 'winston';
4
5const logger = winston.createLogger({
6 level: process.env.LOG_LEVEL || 'info',
7 format: winston.format.combine(
8 winston.format.timestamp(),
9 winston.format.json()
10 ),
11 defaultMeta: { service: 'next-app' },
12 transports: [
13 // Local log saving at error level
14 new winston.transports.File({ filename: 'error.log', level: 'error' }),
15 // Local saving of all logs
16 new winston.transports.File({ filename: 'combined.log' }),
17 ],
18});
19
20// In production, send logs to a monitoring service
21if (process.env.NODE_ENV === 'production') {
22 // Example for Datadog
23 // Wymaga instalacji '@datadog/winston'
24 // import { datadog } from '@datadog/winston';
25 // logger.add(new datadog({
26 // apiKey: process.env.DATADOG_API_KEY,
27 // hostname: process.env.HOSTNAME,
28 // service: 'next-app',
29 // ddsource: 'nodejs',
30 // }));
31}
32
33// Functions for logging security events
34export function logSecurityEvent(
35 eventType: string,
36 data: any,
37 severity: 'info' | 'warn' | 'error' = 'info'
38) {
39 logger.log({
40 level: severity,
41 message: `Security event: ${eventType}`,
42 eventType,
43 data,
44 timestamp: new Date().toISOString(),
45 });
46}
47
48export function logAuthEvent(
49 userId: string | null,
50 action: 'login' | 'logout' | 'failed_login' | 'password_reset' | 'account_locked',
51 details: any = {}
52) {
53 logSecurityEvent(
54 `auth_${action}`,
55 {
56 userId,
57 ip: details.ip,
58 userAgent: details.userAgent,
59 ...details,
60 },
61 action === 'failed_login' || action === 'account_locked' ? 'warn' : 'info'
62 );
63}
64
65// 2. Implementacja middleware do logowania requests HTTP
66// middleware.ts
67import { NextResponse } from 'next/server';
68import type { NextRequest } from 'next/server';
69import { logSecurityEvent } from '@/lib/logger';
70
71export function middleware(request: NextRequest) {
72 // Save basic request information
73 const requestInfo = {
74 method: request.method,
75 path: request.nextUrl.pathname,
76 ip: request.ip || 'unknown',
77 userAgent: request.headers.get('user-agent') || 'unknown',
78 referer: request.headers.get('referer') || 'unknown',
79 };
80
81 // Loguj tylko istotne paths (pomijaj zasoby statyczne, itp.)
82 if (!request.nextUrl.pathname.match(/\.(ico|png|jpg|jpeg|svg|css|js)$/)) {
83 logSecurityEvent('http_request', requestInfo);
84 }
85
86 // Potential attack detection
87 if (detectPotentialAttack(request)) {
88 logSecurityEvent('potential_attack', requestInfo, 'warn');
89 }
90
91 return NextResponse.next();
92}
93
94// Function that detects potential attacks based on request patterns
95function detectPotentialAttack(request: NextRequest): boolean {
96 const url = request.nextUrl.toString().toLowerCase();
97 const body = request.body; // May be unavailable in middleware
98
99 // Detection of basic attack attempts
100 const attackPatterns = [
101 /(\.\.\/|\.\.\\/)/i, // Path traversal
102 /('|")(;|--|\+|\%|\#|\)/i, // Podstawowe SQL injection
103 /<script>[^<]*<\/script>/i, // Basic XSS
104 /etc\/passwd/i, // Path disclosure
105 /\/(wp-admin|wp-content|wp-includes)/i, // WordPress scanning
106 ];
107
108 return attackPatterns.some(pattern => pattern.test(url));
109}Dodatkowe najlepsze praktyki:
- Implementuj centralizowany system logowania i monitorowania
- Konfiguruj automatyczne alertowanie o podejrzanych zdarzeniach
- Provide mechanisms for correlating events from different parts of the application
- Regularly analyze logs for unauthorized activities
- Store logs for an appropriately long time in accordance with regulations and business needs
10. Server-Side Request Forgery
Threat: SSRF attacks allow an attacker to invoke HTTP requests from the application server to internal or external resources to which the server has access.
Zabezpieczenia w Next.js:
1// 1. Secure URL fetching implementation
2// lib/safeFetch.ts
3import { URL } from 'url';
4import fetch from 'node-fetch';
5
6// Lista dozwolonych domen
7const ALLOWED_HOSTS = [
8 'api.example.com',
9 'api.trusted-service.com',
10 'cdn.example.com',
11];
12
13// List of forbidden IP addresses (private IP ranges)
14const BLOCKED_IP_RANGES = [
15 // Lokalne
16 /^127\.\d+\.\d+\.\d+$/, // 127.0.0.0/8
17 /^10\.\d+\.\d+\.\d+$/, // 10.0.0.0/8
18 /^172\.(1[6-9]|2\d|3[0-1])\.\d+\.\d+$/, // 172.16.0.0/12
19 /^192\.168\.\d+\.\d+$/, // 192.168.0.0/16
20 // Lokalne linki
21 /^169\.254\.\d+\.\d+$/, // 169.254.0.0/16
22 // Multicast
23 /^2(2[4-9]|3\d)\.\d+\.\d+\.\d+$/, // 224.0.0.0/4
24];
25
26export async function safeFetch(urlString: string, options: RequestInit = {}) {
27 try {
28 // Parse URL to check its components
29 const url = new URL(urlString);
30
31 // Check if the domain is on the allowed list
32 if (!ALLOWED_HOSTS.includes(url.hostname)) {
33 throw new Error(`Host not allowed: ${url.hostname}`);
34 }
35
36 // Check if the IP address is not on the blocked list
37 // To wymaga dodatkowej logiki do rozpoznawania DNS
38 // Simplified version of checking directly by host
39 if (BLOCKED_IP_RANGES.some(pattern => pattern.test(url.hostname))) {
40 throw new Error(`IP address not allowed: ${url.hostname}`);
41 }
42
43 // Check if the protocol is secure
44 if (url.protocol !== 'https:') {
45 throw new Error(`Protocol not allowed: ${url.protocol}`);
46 }
47
48 // Wykonaj request HTTP po weryfikacji URL
49 return fetch(url.toString(), options);
50 } catch (error) {
51 console.error('Safe fetch error:', error);
52 throw new Error(`Invalid or disallowed URL: ${urlString}`);
53 }
54}
55
56// 2. API proxy implementation with URL validation
57// app/api/proxy/route.ts
58import { NextResponse } from 'next/server';
59import { safeFetch } from '@/lib/safeFetch';
60
61export async function GET(request: Request) {
62 try {
63 const { searchParams } = new URL(request.url);
64 const targetUrl = searchParams.get('url');
65
66 if (!targetUrl) {
67 return NextResponse.json(
68 { error: 'URL parameter is required' },
69 { status: 400 }
70 );
71 }
72
73 try {
74 // Use the secure fetch function
75 const response = await safeFetch(targetUrl);
76 const data = await response.text();
77
78 return new NextResponse(data, {
79 status: response.status,
80 headers: {
81 'Content-Type': response.headers.get('Content-Type') || 'text/plain',
82 },
83 });
84 } catch (error) {
85 return NextResponse.json(
86 { error: 'Invalid or disallowed URL' },
87 { status: 400 }
88 );
89 }
90 } catch (error) {
91 console.error('Proxy error:', error);
92 return NextResponse.json(
93 { error: 'Server error' },
94 { status: 500 }
95 );
96 }
97}Dodatkowe najlepsze praktyki:
- Implementuj listy dozwolonych domen zamiast list blokowanych
- Use a network firewall to block connections to internal resources
- Filter and sanitize input data containing URLs
- Use minimal permissions for service and application accounts
- Avoid passing full responses from external APIs to the client
Implementacja Security Headers w Next.js
Security headers are a key element in protecting web applications. Next.js allows easy configuration of these headers in the file next.config.js:
1// next.config.js
2const securityHeaders = [
3 {
4 key: 'X-DNS-Prefetch-Control',
5 value: 'on',
6 },
7 {
8 key: 'Strict-Transport-Security',
9 value: 'max-age=63072000; includeSubDomains; preload',
10 },
11 {
12 key: 'X-XSS-Protection',
13 value: '1; mode=block',
14 },
15 {
16 key: 'X-Frame-Options',
17 value: 'SAMEORIGIN',
18 },
19 {
20 key: 'X-Content-Type-Options',
21 value: 'nosniff',
22 },
23 {
24 key: 'Referrer-Policy',
25 value: 'strict-origin-when-cross-origin',
26 },
27 {
28 key: 'Permissions-Policy',
29 value: 'camera=(), microphone=(), geolocation=(self), interest-cohort=()',
30 },
31];
32
33module.exports = {
34 async headers() {
35 return [
36 {
37 // Apply these headers to all paths
38 source: '/(.*)',
39 headers: securityHeaders,
40 },
41 ];
42 },
43 // Inne konfiguracje Next.js...
44};Penetration testing and security testing automation
Tests are a key element in ensuring application security. Below is an example of integrating security tests with the CI/CD process:
1# .github/workflows/security.yml
2name: Security Checks
3
4on:
5 push:
6 branches: [ main, develop ]
7 pull_request:
8 branches: [ main, develop ]
9 schedule:
10 - cron: '0 0 * * 0' # Cotygodniowe skanowanie
11
12jobs:
13 security-checks:
14 runs-on: ubuntu-latest
15 steps:
16 - uses: actions/checkout@v2
17
18 - name: Setup Node.js
19 uses: actions/setup-node@v2
20 with:
21 node-version: '16'
22
23 - name: Install dependencies
24 run: npm ci
25
26 - name: Run npm audit
27 run: npm audit --audit-level=high
28
29 - name: OWASP ZAP Scan
30 uses: zaproxy/action-baseline@v0.6.1
31 with:
32 target: 'https://staging.example.com' # Your staging environment URL
33
34 - name: Run Snyk to check for vulnerabilities
35 uses: snyk/actions/node@master
36 env:
37 SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
38
39 - name: Check for secrets in code
40 uses: gitleaks/gitleaks-action@v1.6.0Security checklist for Next.js applications
Below is a checklist you can use to assess the security of your Next.js application:
General security
- Appropriate security-related HTTP headers have been implemented
- HTTPS is used for all environments
- Input data validation mechanism has been implemented
- Inactive sessions are automatically logged out
- Sensitive information is not stored in publicly accessible files
- Logs do not contain sensitive data
Uwierzytelnianie i authorization
- Passwords are properly hashed and salted
- Login attempt limiting mechanism has been implemented
- Password security policy has been implemented
- Role and permission system works correctly
- Sessions are securely managed
- Multi-factor authentication (MFA) has been implemented for administrator accounts
Traitement des données
- Data is properly sanitized before rendering
- Database queries are parameterized (protected against SQL Injection)
- XSS prevention mechanisms have been implemented
- API endpoints are protected against unauthorized access
- CSRF token implementation prevents CSRF attacks
Dependencies and code
- All dependencies are up to date and free of known vulnerabilities
- Code is regularly scanned for security vulnerabilities
- Best coding practices are applied
- There are no hardcoded secrets and keys in the code
Monitorowanie i reakcja na incydenty
- Security event logging mechanism has been implemented
- Security incident response procedures have been defined
- Regular penetration tests are conducted
- Monitoring and alerting mechanisms have been implemented
Summary
Web application security, including Next.js applications, requires a comprehensive approach and constant attention. In this module, we covered the most important threats defined in the OWASP Top 10 and how to minimize them in the context of Next.js applications.
Key points to remember:
- Comprehensive approach - security should be considered at every stage of application development
- Principle of least permissions - grant minimum permissions necessary to perform the task
- Validation and sanitization - always validate and sanitize input data
- Updates and patches - regularly update dependencies and apply security patches
- Monitorowanie i logowanie - implementuj rozbudowane mechanizmy monitorowania i logowania
- Testing - regularly perform security tests
Remember that security is a process, not a product. It requires constant improvement and adaptation to new threats.
In the next module, we will look at security auditing and monitoring to better understand how to detect and respond to potential security incidents.
Code de cette leçon : App.tsx
1import React, { useState } from 'react';
2
3// Demo: OWASP Top 10
4interface Vulnerability {
5 id: string;
6 name: string;
7 owaspId: string;
8 severity: 'critical' | 'high' | 'medium';
9 description: string;
10 example: string;
11 prevention: string[];
12}
13
14const owaspTop10: Vulnerability[] = [
15 { id: '1', name: 'Broken Access Control', owaspId: 'A01:2021', severity: 'critical',
16 description: 'Absence de contrôle d\'accès - les utilisateurs agissent au-delà de leurs permissions.',
17 example: 'GET /api/users/123/data - no identity verification',
18 prevention: ['Refuse l\'accès par défaut', 'Implémente le RBAC', 'Valide côté serveur'] },
19 { id: '2', name: 'Cryptographic Failures', owaspId: 'A02:2021', severity: 'critical',
20 description: 'Chiffrement faible ou absence de chiffrement des données.',
21 example: 'Mots de passe en clair dans la base de données',
22 prevention: ['bcrypt/argon2 pour les mots de passe', 'HTTPS', 'Ne stocke pas de données inutiles'] },
23 { id: '3', name: 'Injection', owaspId: 'A03:2021', severity: 'critical',
24 description: 'SQL, NoSQL, OS command injection.',
25 example: 'SELECT * FROM users WHERE id = userInput (sans paramétrage)',
26 prevention: ['Requêtes paramétrées', 'Validation des données', 'Utilise un ORM'] },
27 { id: '4', name: 'Insecure Design', owaspId: 'A04:2021', severity: 'high',
28 description: 'Absence de patterns de sécurité dans l\'architecture.',
29 example: 'Absence de rate limiting sur la connexion',
30 prevention: ['Modélisation des menaces', 'Patterns de sécurité', 'Security code review'] },
31 { id: '5', name: 'Security Misconfiguration', owaspId: 'A05:2021', severity: 'high',
32 description: 'Paramètres par défaut, ports ouverts, debug en production.',
33 example: 'Erreurs détaillées en production',
34 prevention: ['Hardening', 'Permissions minimales', 'Audits de configuration'] },
35 { id: '6', name: 'Vulnerable Components', owaspId: 'A06:2021', severity: 'medium',
36 description: 'Bibliothèques avec des vulnérabilités connues.',
37 example: 'Ancienne version de lodash avec prototype pollution',
38 prevention: ['npm audit', 'Mets à jour les dépendances', 'Surveille les CVE'] },
39 { id: '7', name: 'Auth Failures', owaspId: 'A07:2021', severity: 'critical',
40 description: 'Mots de passe faibles, absence de MFA, tokens vulnérables.',
41 example: 'Absence de limite de tentatives de connexion',
42 prevention: ['MFA', 'Politiques de mots de passe forts', 'Rate limiting'] },
43 { id: '8', name: 'Data Integrity', owaspId: 'A08:2021', severity: 'high',
44 description: 'Erreurs d\'intégrité logicielle et des données.',
45 example: 'CI/CD sans vérification des artefacts',
46 prevention: ['Signature des artefacts', 'Vérification de l\'intégrité', 'CI/CD sécurisé'] },
47];
48
49const sevColors: Record<string, string> = { critical: '#ef4444', high: '#f59e0b', medium: '#3b82f6' };
50
51function OWASPDemo() {
52 const [selected, setSelected] = useState<Vulnerability | null>(null);
53 const [checked, setChecked] = useState<Set<string>>(new Set());
54 const toggle = (id: string) => setChecked(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
55 const score = Math.round((checked.size / owaspTop10.length) * 100);
56
57 return (
58 <div style={{ background: '#0f0f23', minHeight: '100vh', padding: '20px', color: '#e0e0e0', fontFamily: 'system-ui' }}>
59 <h1 style={{ color: '#64ffda', marginBottom: '8px' }}>OWASP Top 10 Checklist</h1>
60
61 <div style={{ display: 'flex', alignItems: 'center', gap: '16px', marginBottom: '24px', padding: '16px', background: '#1a1a2e', borderRadius: '8px', border: '1px solid #333' }}>
62 <div style={{ width: '60px', height: '60px', borderRadius: '50%', border: '3px solid ' + (score >= 75 ? '#10b981' : score >= 50 ? '#f59e0b' : '#ef4444'), display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px', fontWeight: 'bold', color: score >= 75 ? '#10b981' : score >= 50 ? '#f59e0b' : '#ef4444' }}>{score}%</div>
63 <div><strong>Security Score</strong><div style={{ fontSize: '13px', color: '#8892b0' }}>{checked.size}/{owaspTop10.length} sécurisés</div></div>
64 </div>
65
66 <div style={{ display: 'grid', gridTemplateColumns: selected ? '1fr 1fr' : '1fr', gap: '20px' }}>
67 <div style={{ display: 'grid', gap: '8px' }}>
68 {owaspTop10.map(v => (
69 <div key={v.id} onClick={() => setSelected(v)} style={{
70 padding: '12px 16px', borderRadius: '8px', cursor: 'pointer',
71 background: selected?.id === v.id ? '#1a2a3e' : '#1a1a2e',
72 border: selected?.id === v.id ? '1px solid #64ffda' : '1px solid #333',
73 display: 'flex', alignItems: 'center', gap: '12px',
74 }}>
75 <input type="checkbox" checked={checked.has(v.id)} onChange={() => toggle(v.id)} onClick={e => e.stopPropagation()} />
76 <span style={{ fontSize: '12px', padding: '2px 8px', borderRadius: '12px', background: sevColors[v.severity] + '30', color: sevColors[v.severity] }}>{v.severity}</span>
77 <strong style={{ fontSize: '14px' }}>{v.owaspId}: {v.name}</strong>
78 </div>
79 ))}
80 </div>
81 {selected && (
82 <div style={{ background: '#1a1a2e', padding: '20px', borderRadius: '12px', border: '1px solid #333' }}>
83 <h3 style={{ color: '#64ffda', marginTop: 0 }}>{selected.owaspId}: {selected.name}</h3>
84 <p style={{ color: '#8892b0' }}>{selected.description}</p>
85 <div style={{ background: '#0f0f23', padding: '12px', borderRadius: '6px', marginBottom: '12px' }}>
86 <strong style={{ color: '#ef4444', fontSize: '13px' }}>Exemple :</strong>
87 <pre style={{ fontSize: '12px', color: '#f59e0b', margin: '8px 0 0', whiteSpace: 'pre-wrap' }}>{selected.example}</pre>
88 </div>
89 <strong style={{ color: '#10b981', fontSize: '13px' }}>Prévention :</strong>
90 <ul style={{ fontSize: '13px', color: '#8892b0', paddingLeft: '20px', margin: '8px 0 0' }}>
91 {selected.prevention.map((p, i) => <li key={i}>{p}</li>)}
92 </ul>
93 </div>
94 )}
95 </div>
96 </div>
97 );
98}
99
100export default OWASPDemo;Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. L'authentification multifacteur (MFA) ajoute :
2. Les attaques CSRF peuvent être évitées par :
Voici 2 des 6 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Tri vertical
Place la syntaxe d'import et d'utilisation de redirect
- Clique dans l'ordre
Place la structure du jeton JWT :
- Tri vertical
Place les éléments dans le bon ordre : Suivre les requêtes → Vérifier les limites → Bloquer/Auto
- Tri vertical
Place le cycle de vie du jeton JWT :