Helmet va CORS sozlash

~15 daq · NestJS · Fayllar va Xavfsizlik

Muammodan boshlaylik

Ikki muammo:

1. CORS. Frontend http://localhost:5173 da, backend http://localhost:3000 da. Frontend so'rov yuborganida brauzer bloklab qo'ydi: Cross-Origin Request Blocked. Boshqa domendan kelgan so'rov — brauzer xavfsizlik siyosati.

2. Xavfsizlik headerlari. X-Powered-By: Express — xaker qaysi framework ekanini bildi. Keyingi hujum maqsadliroq bo'ldi. Onlayn skaner saytingizda Clickjacking, MIME sniffing zaifliklarini topdi.

Helmet va CORS — ikkisini bir darsda hal qilamiz.


Helmet — xavfsizlik headerlari

Helmet — HTTP response headerlariga xavfsizlik sozlamalarini qo'shadigan middleware.

npm install helmet
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import helmet from 'helmet';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(helmet()); // ← barcha Helmet headerlarini qo'shadi

  await app.listen(3000);
}

Helmet qo'shadigan headerlar:

Content-Security-Policy    — qaysi resurslar yuklana olishini belgilaydi
X-DNS-Prefetch-Control     — DNS prefetch ni boshqaradi
X-Frame-Options: SAMEORIGIN — Clickjacking dan himoya
X-Content-Type-Options     — MIME sniffing dan himoya
Referrer-Policy            — referrer ma'lumotini cheklaydi
Strict-Transport-Security  — faqat HTTPS (HSTS)
X-Powered-By               — olib tashlanadi (framework yashiriladi)

Sozlash

app.use(
  helmet({
    // Content Security Policy — qaysi domenlardan resurs yuklash mumkin
    contentSecurityPolicy: {
      directives: {
        defaultSrc:  ["'self'"],
        scriptSrc:   ["'self'", "'unsafe-inline'"],  // inline JS ruxsat
        styleSrc:    ["'self'", "'unsafe-inline'", 'fonts.googleapis.com'],
        imgSrc:      ["'self'", 'data:', '*.amazonaws.com'],
        connectSrc:  ["'self'", 'api.myapp.uz'],
        fontSrc:     ["'self'", 'fonts.gstatic.com'],
        objectSrc:   ["'none'"],
        upgradeInsecureRequests: [],
      },
    },

    // HSTS — faqat HTTPS (development da o'chiriladi)
    strictTransportSecurity: process.env.NODE_ENV === 'production'
      ? { maxAge: 31536000, includeSubDomains: true }
      : false,

    // Clickjacking — iframe da ko'rsatish
    frameguard: { action: 'sameorigin' }, // faqat bir xil domen
  }),
);

CORS — Cross-Origin Resource Sharing

CORS — brauzer xavfsizlik mexanizmi. Boshqa domendan kelgan so'rovlarni brauzer bloklab qo'yadi. Server "men bu domenga ruxsat beraman" desa — brauzer o'tkazadi.

// src/main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Oddiy sozlash — development uchun
  app.enableCors({
    origin: 'http://localhost:5173', // faqat shu frontend
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true, // cookie va Authorization header uchun muhim
  });

  await app.listen(3000);
}

Ko'p domen — whitelist

// src/main.ts
const ALLOWED_ORIGINS = [
  'http://localhost:5173',       // dev frontend
  'http://localhost:3001',       // dev admin panel
  'https://myapp.uz',           // prod frontend
  'https://admin.myapp.uz',     // prod admin
];

app.enableCors({
  origin: (origin, callback) => {
    // origin yo'q bo'lsa (Postman, server-to-server) — ruxsat
    if (!origin) return callback(null, true);

    if (ALLOWED_ORIGINS.includes(origin)) {
      callback(null, true);  // ← ruxsat
    } else {
      callback(new Error(`CORS: ${origin} ruxsat yo'q`)); // ← blok
    }
  },
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  credentials: true,
  maxAge: 86400, // Preflight so'rovni 24 soat keshlaymiz
});

Environment bo'yicha sozlash

// src/main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const config = app.get(ConfigService);
  const isDev = config.get('NODE_ENV') !== 'production';

  app.use(helmet({
    contentSecurityPolicy: isDev ? false : { /* prod CSP */ },
    strictTransportSecurity: isDev ? false : { maxAge: 31536000 },
  }));

  app.enableCors({
    origin: isDev
      ? true                                    // dev — hammaga ruxsat
      : [config.get('FRONTEND_URL'),            // prod — whitelist
         config.get('ADMIN_URL')],
    credentials: true,
  });

  await app.listen(config.get('PORT', 3000));
}

Tez-tez uchraydigan xatolar

credentials: true bilan origin: '*' birga ishlamaydi

// ❌ Brauzer ruxsat bermaydi
app.enableCors({
  origin: '*',         // barcha domenlar
  credentials: true,  // cookie — birga ishlamaydi
});

// ✅ Aniq domen ko'rsatilishi kerak
app.enableCors({
  origin: 'https://myapp.uz',
  credentials: true,
});

Helmet Content-Security-Policy inline skriptlarni bloklaydi

// ❌ Brauzerda inline <script> yoki eval ishlaydi — CSP bloklab qo'yadi
// Natija: sahifa buzilgan ko'rinadi

// ✅ Dev da CSP ni o'chirish
app.use(helmet({ contentSecurityPolicy: false }));

// Yoki nonce bilan — ilg'or mavzu

OPTIONS so'rovga javob yo'q

Brauzer preflight: OPTIONS /api/users → Server: 404

Sabab: OPTIONS method route da yo'q

✅ Yechim: CORS options ichida 'OPTIONS' qo'shish yoki:
app.enableCors() — OPTIONS ni avtomatik boshqaradi

Xulosa

Helmet — xavfsizlik headerlarini bir qatorda. CORS — qaysi frontendlarga ruxsat berishni belgilash.

Eslab qoling:

  • app.use(helmet()) — standart xavfsizlik headerlari
  • app.enableCors({ origin, credentials }) — CORS sozlash
  • credentials: true bilan origin: '*' birga ishlamaydi
  • Dev da soddaroq, prod da qattiqroq sozlash
  • ALLOWED_ORIGINS whitelist — noaniq domen bloklanadi

Keyingi dars

35-dars: Compression — gzip, brotli