Bezpieczeństwo aplikacji React to kluczowy aspekt nowoczesnego rozwoju oprogramowania. W tej lekcji poznamy kompleksowe praktyki bezpieczeństwa, począwszy od podstawowych zabezpieczeń przed XSS, aż po zaawansowane techniki ochrony zgodne z OWASP Top 10.
React domyślnie chroni przed XSS poprzez automatyczne escapowanie:
1// React automatycznie escapuje te wartości
2function UserProfile({ userData }) {
3 return (
4 <div>
5 {/* Bezpieczne - React escapuje zawartość */}
6 <h1>{userData.name}</h1>
7 <p>{userData.description}</p>
8
9 {/* NIEBEZPIECZNE - omija escapowanie */}
10 <div dangerouslySetInnerHTML={{ __html: userData.bio }} />
11 </div>
12 );
13}1// Bezpieczne komponenty z walidacją
2function SafeHTMLRenderer({ content, allowedTags = [] }) {
3 // Biblioteka DOMPurify do oczyszczania HTML
4 const cleanHTML = DOMPurify.sanitize(content, {
5 ALLOWED_TAGS: allowedTags,
6 ALLOWED_ATTR: ['href', 'title', 'alt', 'src'],
7 ALLOW_DATA_ATTR: false
8 });
9
10 return (
11 <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
12 );
13}
14
15// Walidacja URL-i
16function SafeLink({ href, children, ...props }) {
17 const isValidURL = (url) => {
18 try {
19 const urlObj = new URL(url);
20 // Zezwalaj tylko na bezpieczne protokoły
21 return ['http:', 'https:', 'mailto:'].includes(urlObj.protocol);
22 } catch {
23 return false;
24 }
25 };
26
27 if (!isValidURL(href)) {
28 console.warn('Niebezpieczny URL zablokowany:', href);
29 return <span>{children}</span>;
30 }
31
32 return (
33 <a
34 href={href}
35 {...props}
36 rel="noopener noreferrer" // Zabezpieczenie przed tabnabbing
37 >
38 {children}
39 </a>
40 );
41}
42
43// Bezpieczne renderowanie obrazów
44function SafeImage({ src, alt, ...props }) {
45 const [imageSrc, setImageSrc] = useState(null);
46 const [error, setError] = useState(false);
47
48 useEffect(() => {
49 // Walidacja źródła obrazu
50 if (src && typeof src === 'string') {
51 try {
52 const url = new URL(src);
53 if (['http:', 'https:', 'data:'].includes(url.protocol)) {
54 setImageSrc(src);
55 } else {
56 setError(true);
57 }
58 } catch {
59 setError(true);
60 }
61 }
62 }, [src]);
63
64 if (error) {
65 return <div className="image-error">Nieprawidłowe źródło obrazu</div>;
66 }
67
68 return imageSrc ? (
69 <img
70 src={imageSrc}
71 alt={alt}
72 {...props}
73 onError={() => setError(true)}
74 />
75 ) : null;
76}1// CSP Header configuration
2const cspConfig = {
3 'default-src': ["'self'"],
4 'script-src': [
5 "'self'",
6 "'unsafe-inline'", // Tylko w development
7 'https://apis.google.com',
8 'https://cdn.jsdelivr.net'
9 ],
10 'style-src': [
11 "'self'",
12 "'unsafe-inline'", // Dla CSS-in-JS
13 'https://fonts.googleapis.com'
14 ],
15 'img-src': [
16 "'self'",
17 'data:',
18 'https:',
19 'blob:'
20 ],
21 'font-src': [
22 "'self'",
23 'https://fonts.gstatic.com'
24 ],
25 'connect-src': [
26 "'self'",
27 'https://api.example.com',
28 'wss://socket.example.com'
29 ],
30 'frame-ancestors': ["'none'"],
31 'form-action': ["'self'"],
32 'upgrade-insecure-requests': []
33};
34
35// Next.js CSP configuration
36// next.config.js
37const securityHeaders = [
38 {
39 key: 'Content-Security-Policy',
40 value: Object.entries(cspConfig)
41 .map(([directive, sources]) =>
42 `${directive} ${sources.join(' ')}`
43 )
44 .join('; ')
45 },
46 {
47 key: 'X-Frame-Options',
48 value: 'DENY'
49 },
50 {
51 key: 'X-Content-Type-Options',
52 value: 'nosniff'
53 },
54 {
55 key: 'Referrer-Policy',
56 value: 'strict-origin-when-cross-origin'
57 },
58 {
59 key: 'Permissions-Policy',
60 value: 'camera=(), microphone=(), geolocation=()'
61 }
62];
63
64module.exports = {
65 async headers() {
66 return [
67 {
68 source: '/(.*)',
69 headers: securityHeaders
70 }
71 ];
72 }
73};1// Secure JWT storage and handling
2class SecureTokenManager {
3 constructor() {
4 this.tokenKey = 'auth_token';
5 this.refreshTokenKey = 'refresh_token';
6 }
7
8 // Przechowywanie w httpOnly cookies (najlepsze)
9 setTokens(accessToken, refreshToken) {
10 // Wysyłanie na backend do ustawienia httpOnly cookies
11 fetch('/api/auth/set-tokens', {
12 method: 'POST',
13 headers: { 'Content-Type': 'application/json' },
14 body: JSON.stringify({ accessToken, refreshToken })
15 });
16 }
17
18 // Alternatywnie - sessionStorage (mniej bezpieczne)
19 setTokensInStorage(accessToken, refreshToken) {
20 // NIGDY nie używaj localStorage dla tokenów!
21 sessionStorage.setItem(this.tokenKey, accessToken);
22
23 if (refreshToken) {
24 // Refresh token w bezpiecznym miejscu
25 this.storeRefreshTokenSecurely(refreshToken);
26 }
27 }
28
29 getToken() {
30 return sessionStorage.getItem(this.tokenKey);
31 }
32
33 removeTokens() {
34 sessionStorage.removeItem(this.tokenKey);
35 sessionStorage.removeItem(this.refreshTokenKey);
36
37 // Wyloguj także z cookies
38 fetch('/api/auth/logout', { method: 'POST' });
39 }
40
41 // Walidacja tokenu
42 isTokenValid(token) {
43 if (!token) return false;
44
45 try {
46 const payload = JSON.parse(atob(token.split('.')[1]));
47 const now = Date.now() / 1000;
48
49 // Sprawdź czy token nie wygasł
50 return payload.exp > now;
51 } catch {
52 return false;
53 }
54 }
55
56 // Automatyczne odświeżanie tokenu
57 async refreshTokenIfNeeded() {
58 const token = this.getToken();
59
60 if (!token || !this.isTokenValid(token)) {
61 return await this.refreshToken();
62 }
63
64 return token;
65 }
66
67 async refreshToken() {
68 try {
69 const response = await fetch('/api/auth/refresh', {
70 method: 'POST',
71 credentials: 'include' // Dla httpOnly cookies
72 });
73
74 if (response.ok) {
75 const { accessToken } = await response.json();
76 sessionStorage.setItem(this.tokenKey, accessToken);
77 return accessToken;
78 }
79 } catch (error) {
80 console.error('Token refresh failed:', error);
81 this.removeTokens();
82 }
83
84 return null;
85 }
86}
87
88// Hook dla autoryzacji
89function useAuth() {
90 const [user, setUser] = useState(null);
91 const [loading, setLoading] = useState(true);
92 const tokenManager = useRef(new SecureTokenManager());
93
94 const login = async (credentials) => {
95 try {
96 const response = await fetch('/api/auth/login', {
97 method: 'POST',
98 headers: { 'Content-Type': 'application/json' },
99 body: JSON.stringify(credentials)
100 });
101
102 if (response.ok) {
103 const { user, accessToken, refreshToken } = await response.json();
104
105 // Bezpieczne przechowywanie tokenów
106 tokenManager.current.setTokens(accessToken, refreshToken);
107 setUser(user);
108
109 return { success: true };
110 }
111 } catch (error) {
112 return { success: false, error: error.message };
113 }
114 };
115
116 const logout = () => {
117 tokenManager.current.removeTokens();
118 setUser(null);
119 };
120
121 // Automatyczne odświeżanie tokenu
122 useEffect(() => {
123 const interval = setInterval(async () => {
124 await tokenManager.current.refreshTokenIfNeeded();
125 }, 5 * 60 * 1000); // Co 5 minut
126
127 return () => clearInterval(interval);
128 }, []);
129
130 return { user, login, logout, loading };
131}1// Secure Axios configuration
2const createSecureAxiosInstance = () => {
3 const tokenManager = new SecureTokenManager();
4
5 const axiosInstance = axios.create({
6 baseURL: process.env.REACT_APP_API_URL,
7 timeout: 10000,
8 withCredentials: true // Dla httpOnly cookies
9 });
10
11 // Request interceptor
12 axiosInstance.interceptors.request.use(
13 async (config) => {
14 const token = await tokenManager.refreshTokenIfNeeded();
15
16 if (token) {
17 config.headers.Authorization = `Bearer ${token}`;
18 }
19
20 // Dodaj CSRF token jeśli dostępny
21 const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
22 if (csrfToken) {
23 config.headers['X-CSRF-Token'] = csrfToken;
24 }
25
26 return config;
27 },
28 (error) => Promise.reject(error)
29 );
30
31 // Response interceptor
32 axiosInstance.interceptors.response.use(
33 (response) => response,
34 async (error) => {
35 const originalRequest = error.config;
36
37 if (error.response?.status === 401 && !originalRequest._retry) {
38 originalRequest._retry = true;
39
40 const newToken = await tokenManager.refreshToken();
41
42 if (newToken) {
43 originalRequest.headers.Authorization = `Bearer ${newToken}`;
44 return axiosInstance(originalRequest);
45 } else {
46 // Przekieruj do logowania
47 window.location.href = '/login';
48 }
49 }
50
51 return Promise.reject(error);
52 }
53 );
54
55 return axiosInstance;
56};1// .env.example - template dla zespołu
2REACT_APP_API_URL=https://api.example.com
3REACT_APP_ENVIRONMENT=development
4REACT_APP_SENTRY_DSN=your_sentry_dsn_here
5
6# Sekrety (NIE DODAWAJ do .env.example)
7# API_SECRET_KEY=your_secret_here
8# DATABASE_URL=your_db_url_here1// Walidacja zmiennych środowiskowych
2class EnvironmentValidator {
3 static requiredVars = [
4 'REACT_APP_API_URL',
5 'REACT_APP_ENVIRONMENT'
6 ];
7
8 static validate() {
9 const missing = this.requiredVars.filter(
10 varName => !process.env[varName]
11 );
12
13 if (missing.length > 0) {
14 throw new Error(
15 `Missing required environment variables: ${missing.join(', ')}`
16 );
17 }
18 }
19
20 static get(key, defaultValue = null) {
21 const value = process.env[key];
22
23 if (!value && defaultValue === null) {
24 console.warn(`Environment variable ${key} is not set`);
25 }
26
27 return value || defaultValue;
28 }
29
30 static getSecure(key) {
31 const value = process.env[key];
32
33 if (!value) {
34 throw new Error(`Secure environment variable ${key} is required`);
35 }
36
37 return value;
38 }
39}
40
41// Konfiguracja aplikacji
42const appConfig = {
43 apiUrl: EnvironmentValidator.getSecure('REACT_APP_API_URL'),
44 environment: EnvironmentValidator.get('REACT_APP_ENVIRONMENT', 'development'),
45 isDevelopment: process.env.NODE_ENV === 'development',
46 isProduction: process.env.NODE_ENV === 'production',
47
48 // Funkcje pomocnicze
49 get isSecure() {
50 return this.apiUrl.startsWith('https://');
51 },
52
53 get shouldEnableDevTools() {
54 return this.isDevelopment && !this.isProduction;
55 }
56};
57
58// Walidacja przy starcie aplikacji
59EnvironmentValidator.validate();1// ConfigProvider.jsx
2const ConfigContext = createContext();
3
4export function ConfigProvider({ children }) {
5 const config = useMemo(() => {
6 const baseConfig = {
7 apiUrl: process.env.REACT_APP_API_URL,
8 environment: process.env.REACT_APP_ENVIRONMENT,
9 version: process.env.REACT_APP_VERSION || '1.0.0'
10 };
11
12 // Konfiguracja specyficzna dla środowiska
13 const environmentConfigs = {
14 development: {
15 debug: true,
16 logLevel: 'debug',
17 enableMocks: true
18 },
19 staging: {
20 debug: true,
21 logLevel: 'info',
22 enableMocks: false
23 },
24 production: {
25 debug: false,
26 logLevel: 'error',
27 enableMocks: false
28 }
29 };
30
31 return {
32 ...baseConfig,
33 ...environmentConfigs[baseConfig.environment] || environmentConfigs.production
34 };
35 }, []);
36
37 return (
38 <ConfigContext.Provider value={config}>
39 {children}
40 </ConfigContext.Provider>
41 );
42}
43
44export const useConfig = () => {
45 const context = useContext(ConfigContext);
46 if (!context) {
47 throw new Error('useConfig must be used within ConfigProvider');
48 }
49 return context;
50};1// package.json scripts dla security
2{
3 "scripts": {
4 "audit": "npm audit",
5 "audit:fix": "npm audit fix",
6 "audit:force": "npm audit fix --force",
7 "security:check": "npm audit && npm run license:check",
8 "license:check": "license-checker --summary",
9 "deps:update": "npm-check-updates -u",
10 "deps:check": "npm outdated"
11 },
12 "devDependencies": {
13 "license-checker": "^25.0.1",
14 "npm-check-updates": "^16.0.0",
15 "audit-ci": "^6.6.1"
16 }
17}1# .github/workflows/security.yml
2name: Security Checks
3
4on: [push, pull_request]
5
6jobs:
7 security:
8 runs-on: ubuntu-latest
9
10 steps:
11 - uses: actions/checkout@v3
12
13 - name: Setup Node.js
14 uses: actions/setup-node@v3
15 with:
16 node-version: '18'
17 cache: 'npm'
18
19 - name: Install dependencies
20 run: npm ci
21
22 - name: Run npm audit
23 run: npm audit --audit-level high
24
25 - name: Check for known vulnerabilities
26 run: npx audit-ci --high
27
28 - name: License compliance check
29 run: npm run license:check
30
31 - name: SAST with CodeQL
32 uses: github/codeql-action/analyze@v2
33 with:
34 languages: javascript
35
36 - name: Dependency Review
37 uses: actions/dependency-review-action@v3
38 if: github.event_name == 'pull_request'1// SecurityMonitor.jsx
2class SecurityMonitor {
3 constructor() {
4 this.violations = [];
5 this.init();
6 }
7
8 init() {
9 // Monitorowanie CSP violations
10 document.addEventListener('securitypolicyviolation', (e) => {
11 this.reportViolation({
12 type: 'CSP_VIOLATION',
13 directive: e.violatedDirective,
14 blockedURI: e.blockedURI,
15 documentURI: e.documentURI,
16 timestamp: new Date().toISOString()
17 });
18 });
19
20 // Monitorowanie console.error dla potencjalnych problemów bezpieczeństwa
21 const originalError = console.error;
22 console.error = (...args) => {
23 const message = args.join(' ');
24
25 if (this.isSecurityRelated(message)) {
26 this.reportViolation({
27 type: 'SECURITY_ERROR',
28 message,
29 timestamp: new Date().toISOString()
30 });
31 }
32
33 originalError.apply(console, args);
34 };
35 }
36
37 isSecurityRelated(message) {
38 const securityKeywords = [
39 'unsafe-inline',
40 'unsafe-eval',
41 'xss',
42 'csrf',
43 'cors',
44 'unauthorized',
45 'forbidden'
46 ];
47
48 return securityKeywords.some(keyword =>
49 message.toLowerCase().includes(keyword)
50 );
51 }
52
53 reportViolation(violation) {
54 this.violations.push(violation);
55
56 // Wyślij do systemu monitorowania
57 if (process.env.NODE_ENV === 'production') {
58 fetch('/api/security/violations', {
59 method: 'POST',
60 headers: { 'Content-Type': 'application/json' },
61 body: JSON.stringify(violation)
62 }).catch(error => {
63 console.error('Failed to report security violation:', error);
64 });
65 }
66 }
67
68 getViolations() {
69 return [...this.violations];
70 }
71}
72
73// Hook dla monitorowania bezpieczeństwa
74export function useSecurityMonitor() {
75 const monitor = useRef(new SecurityMonitor());
76
77 const reportCustomViolation = useCallback((violation) => {
78 monitor.current.reportViolation({
79 type: 'CUSTOM_VIOLATION',
80 ...violation,
81 timestamp: new Date().toISOString()
82 });
83 }, []);
84
85 return {
86 reportViolation: reportCustomViolation,
87 getViolations: () => monitor.current.getViolations()
88 };
89}1// InputValidator.js
2class InputValidator {
3 static patterns = {
4 email: /^[^s@]+@[^s@]+.[^s@]+$/,
5 phone: /^+?[ds-()]+$/,
6 url: /^https?://.+/,
7 alphanumeric: /^[a-zA-Z0-9]+$/,
8 noSpecialChars: /^[a-zA-Z0-9s-_]+$/
9 };
10
11 static validate(value, rules) {
12 const errors = [];
13
14 // Required validation
15 if (rules.required && (!value || value.trim() === '')) {
16 errors.push('This field is required');
17 }
18
19 if (!value) return errors;
20
21 // Length validation
22 if (rules.minLength && value.length < rules.minLength) {
23 errors.push(`Minimum length is ${rules.minLength} characters`);
24 }
25
26 if (rules.maxLength && value.length > rules.maxLength) {
27 errors.push(`Maximum length is ${rules.maxLength} characters`);
28 }
29
30 // Pattern validation
31 if (rules.pattern && !this.patterns[rules.pattern]?.test(value)) {
32 errors.push(`Invalid ${rules.pattern} format`);
33 }
34
35 // Custom validation
36 if (rules.custom) {
37 const customError = rules.custom(value);
38 if (customError) errors.push(customError);
39 }
40
41 // XSS prevention
42 if (rules.preventXSS && this.containsPotentialXSS(value)) {
43 errors.push('Input contains potentially dangerous content');
44 }
45
46 return errors;
47 }
48
49 static containsPotentialXSS(value) {
50 const xssPatterns = [
51 /<script[^>]*>.*?</script>/gi,
52 /javascript:/gi,
53 /onw+s*=/gi,
54 /<iframe[^>]*>/gi,
55 /<object[^>]*>/gi,
56 /<embed[^>]*>/gi,
57 /evals*(/gi,
58 /expressions*(/gi
59 ];
60
61 return xssPatterns.some(pattern => pattern.test(value));
62 }
63
64 static sanitize(value, options = {}) {
65 if (!value) return value;
66
67 let sanitized = value;
68
69 // Remove HTML tags
70 if (options.stripHTML) {
71 sanitized = sanitized.replace(/<[^>]*>/g, '');
72 }
73
74 // Escape HTML entities
75 if (options.escapeHTML) {
76 sanitized = sanitized
77 .replace(/&/g, '&')
78 .replace(/</g, '<')
79 .replace(/>/g, '>')
80 .replace(/"/g, '"')
81 .replace(/'/g, ''');
82 }
83
84 // Remove null bytes
85 sanitized = sanitized.replace(/�/g, '');
86
87 // Trim whitespace
88 if (options.trim !== false) {
89 sanitized = sanitized.trim();
90 }
91
92 return sanitized;
93 }
94}
95
96// SecureForm component
97function SecureForm({ onSubmit, children }) {
98 const [formData, setFormData] = useState({});
99 const [errors, setErrors] = useState({});
100 const [isSubmitting, setIsSubmitting] = useState(false);
101
102 const validateField = useCallback((name, value, rules) => {
103 const fieldErrors = InputValidator.validate(value, rules);
104
105 setErrors(prev => ({
106 ...prev,
107 [name]: fieldErrors.length > 0 ? fieldErrors : undefined
108 }));
109
110 return fieldErrors.length === 0;
111 }, []);
112
113 const handleInputChange = useCallback((name, value, rules) => {
114 // Sanitize input
115 const sanitizedValue = InputValidator.sanitize(value, {
116 stripHTML: true,
117 escapeHTML: true,
118 trim: true
119 });
120
121 setFormData(prev => ({
122 ...prev,
123 [name]: sanitizedValue
124 }));
125
126 // Validate on change
127 validateField(name, sanitizedValue, rules);
128 }, [validateField]);
129
130 const handleSubmit = async (e, validationRules) => {
131 e.preventDefault();
132 setIsSubmitting(true);
133
134 // Validate all fields
135 let isValid = true;
136 const newErrors = {};
137
138 Object.entries(validationRules).forEach(([fieldName, rules]) => {
139 const fieldValue = formData[fieldName];
140 const fieldErrors = InputValidator.validate(fieldValue, rules);
141
142 if (fieldErrors.length > 0) {
143 newErrors[fieldName] = fieldErrors;
144 isValid = false;
145 }
146 });
147
148 setErrors(newErrors);
149
150 if (isValid) {
151 try {
152 await onSubmit(formData);
153 } catch (error) {
154 console.error('Form submission failed:', error);
155 }
156 }
157
158 setIsSubmitting(false);
159 };
160
161 return (
162 <form onSubmit={handleSubmit}>
163 {React.Children.map(children, child => {
164 if (React.isValidElement(child) && child.props.name) {
165 return React.cloneElement(child, {
166 value: formData[child.props.name] || '',
167 onChange: (e) => handleInputChange(
168 child.props.name,
169 e.target.value,
170 child.props.validationRules
171 ),
172 error: errors[child.props.name],
173 disabled: isSubmitting
174 });
175 }
176 return child;
177 })}
178 </form>
179 );
180}1// OWASP Security Checklist Implementation
2
3// 1. Injection Prevention
4const SQLInjectionPrevention = {
5 // Zawsze używaj prepared statements
6 safeQuery: (query, params) => {
7 // Backend implementation example
8 return db.prepare(query).run(params);
9 },
10
11 // Walidacja parametrów
12 validateParams: (params, schema) => {
13 return schema.validate(params);
14 }
15};
16
17// 2. Broken Authentication Prevention
18const AuthenticationSecurity = {
19 // Silne hasła
20 passwordPolicy: {
21 minLength: 12,
22 requireUppercase: true,
23 requireLowercase: true,
24 requireNumbers: true,
25 requireSpecialChars: true,
26 maxAge: 90 * 24 * 60 * 60 * 1000 // 90 dni
27 },
28
29 // Account lockout
30 loginAttempts: {
31 maxAttempts: 5,
32 lockoutDuration: 15 * 60 * 1000 // 15 minut
33 },
34
35 // Session management
36 sessionConfig: {
37 httpOnly: true,
38 secure: true,
39 sameSite: 'strict',
40 maxAge: 30 * 60 * 1000 // 30 minut
41 }
42};
43
44// 3. Sensitive Data Exposure Prevention
45const DataProtection = {
46 // Szyfrowanie wrażliwych danych
47 encryptSensitiveData: (data) => {
48 // Implementacja szyfrowania
49 return CryptoJS.AES.encrypt(data, process.env.ENCRYPTION_KEY);
50 },
51
52 // Bezpieczne przechowywanie
53 secureStorage: {
54 set: (key, value) => {
55 const encrypted = DataProtection.encryptSensitiveData(value);
56 sessionStorage.setItem(key, encrypted);
57 },
58
59 get: (key) => {
60 const encrypted = sessionStorage.getItem(key);
61 if (encrypted) {
62 const decrypted = CryptoJS.AES.decrypt(encrypted, process.env.ENCRYPTION_KEY);
63 return decrypted.toString(CryptoJS.enc.Utf8);
64 }
65 return null;
66 }
67 }
68};
69
70// 4. XML External Entities (XXE) Prevention
71const XXEPrevention = {
72 // Bezpieczne parsowanie XML
73 parseXMLSafely: (xmlString) => {
74 const parser = new DOMParser();
75 const doc = parser.parseFromString(xmlString, 'text/xml');
76
77 // Sprawdź błędy parsowania
78 const parseError = doc.querySelector('parsererror');
79 if (parseError) {
80 throw new Error('Invalid XML format');
81 }
82
83 return doc;
84 }
85};
86
87// 5. Broken Access Control Prevention
88function ProtectedRoute({ children, requiredRole, requiredPermissions }) {
89 const { user, hasRole, hasPermissions } = useAuth();
90
91 if (!user) {
92 return <Redirect to="/login" />;
93 }
94
95 if (requiredRole && !hasRole(requiredRole)) {
96 return <div>Access Denied: Insufficient role</div>;
97 }
98
99 if (requiredPermissions && !hasPermissions(requiredPermissions)) {
100 return <div>Access Denied: Insufficient permissions</div>;
101 }
102
103 return children;
104}
105
106// 6. Security Misconfiguration Prevention
107const SecurityConfig = {
108 // Headers bezpieczeństwa
109 securityHeaders: {
110 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
111 'X-Content-Type-Options': 'nosniff',
112 'X-Frame-Options': 'DENY',
113 'X-XSS-Protection': '1; mode=block',
114 'Referrer-Policy': 'strict-origin-when-cross-origin'
115 },
116
117 // Konfiguracja CORS
118 corsConfig: {
119 origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
120 credentials: true,
121 optionsSuccessStatus: 200
122 }
123};Kompleksowe praktyki bezpieczeństwa w React wymagają holistycznego podejścia, obejmującego wszystkie warstwy aplikacji - od klienta po serwer. Kluczem jest regularne audytowanie, aktualizowanie zależności i przestrzeganie najlepszych praktyk branżowych zgodnych z OWASP Top 10.