Socket.io — autentifikatsiya, middleware

~20 daq · Express · Real-time

Muammodan boshlaylik

Chat ilovangiz tayyor. Lekin bir muammo bor:

Har kim istalgan xonaga kirishi mumkin. Hech qanday tekshiruv yo'q. Kim ekanligini bilmaysiz. "Ali" deb kim xohlasa o'zini tanishtirishi mumkin.

HTTP da bu oson hal qilinadi — JWT middleware, Authorization header. Lekin Socket.io da HTTP so'rov bir marta keladi (handshake paytida), keyin ulanish saqlanib qoladi. req va res yo'q. Qanday tekshirasiz?


Socket.io Middleware nima?

Socket.io o'zining middleware tizimiga ega. HTTP middleware ga o'xshaydi, lekin faqat ulanish paytida ishlaydi — har bir xabar uchun emas.

Foydalanuvchi           Socket.io Server
    |                        |
    |--- handshake (auth) -->|
    |                        |--- middleware 1 (auth tekshirish)
    |                        |--- middleware 2 (log)
    |                        |--- connection handler
    |<-- ulandi -------------|
    |                        |
    |=== xabar yuborish =====>|  ← bu yerda middleware qayta ishlamaydi

Middleware ulanish o'rnatilishidan oldin ishlaydi. Tokeni yaroqsiz bo'lsa — ulanish bekor qilinadi.


Hayotiy o'xshatma

Aeroport kirish nazorati. Samolyotga chiqishdan oldin — bitta tekshiruv punkti. Pasportingiz yo'q — kiritmaydi. Pasportingiz bor — ichiga o'tasiz. Ichkari kirgandan keyin har qadam uchun qayta tekshirilmaysiz.

Socket.io middleware ham shunday: bitta marta, ulanishda. Muvaffaqiyatli o'tsa — sessiya davomida to'liq huquqli.


JWT bilan autentifikatsiya

Ulanish paytida token yuborish

Mijoz JWT ni handshake paytida auth obyekti orqali yuboradi:

// Mijoz (React / JS)
import { io } from 'socket.io-client';

const token = localStorage.getItem('token'); // ← JWT

const socket = io('http://localhost:4000', {
  auth: {
    token, // ← handshake bilan birga ketadi
  },
});

Server middleware

import jwt from 'jsonwebtoken';

io.use((socket, next) => {
  const token = socket.handshake.auth?.token; // ← auth dan olish

  if (!token) {
    return next(new Error('Token yo\'q')); // ← ulanishni rad etish
  }

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    socket.data.user = payload; // ← ma'lumotni socketga saqlash
    next(); // ← davom etish
  } catch (err) {
    next(new Error('Token noto\'g\'ri')); // ← rad etish
  }
});

io.on('connection', (socket) => {
  // Endi foydalanuvchi ma'lumoti mavjud
  console.log('Ulandi:', socket.data.user.email);

  socket.on('xabar', (matn) => {
    // socket.data.user — ishonchli ma'lumot (JWT dan)
    io.emit('yangi-xabar', {
      muallif: socket.data.user.name,
      matn,
    });
  });
});

Mijozda xatoni qabul qilish

socket.on('connect_error', (err) => {
  console.log('Ulanish xatosi:', err.message); // "Token yo'q" yoki "Token noto'g'ri"
  
  if (err.message === 'Token yo\'q') {
    // Login sahifasiga yo'naltirish
    window.location.href = '/login';
  }
});

Namespace middleware

Namespace darajasida middleware — faqat o'sha namespace ga tegishli:

const adminNs = io.of('/admin');

// Faqat admin namespace ga ulanuvchilar uchun
adminNs.use((socket, next) => {
  const user = socket.data.user;

  if (!user) {
    return next(new Error('Autentifikatsiya talab qilinadi'));
  }

  if (user.role !== 'admin') {
    return next(new Error('Ruxsat yo\'q')); // ← admin emas
  }

  next();
});

adminNs.on('connection', (socket) => {
  console.log('Admin ulandi:', socket.data.user.email);
  // admin funksiyalari...
});

Xona ruxsatini tekshirish

Middleware faqat ulanishda ishlaydi. Xonaga kirishda ham tekshirish kerak bo'lishi mumkin — bu connection handler ichida qilinadi:

io.on('connection', (socket) => {
  socket.on('xonaga-kir', async ({ xonaId }) => {
    const user = socket.data.user;

    // Foydalanuvchi bu xonaga kirish huquqiga egami?
    const ruxsat = await db.xonaAzolari.findOne({
      where: { xonaId, userId: user.id },
    });

    if (!ruxsat) {
      // Xatoni faqat shu socketga qaytarish
      socket.emit('xato', { xabar: 'Bu xonaga kirish ruxsati yo\'q' });
      return;
    }

    socket.join(xonaId);
    socket.to(xonaId).emit('foydalanuvchi-kirdi', { ism: user.name });
  });
});

Tez-tez uchraydigan xatolar

1. Middleware da next() ni chaqirmaslik

// ❌ — ulanish muzlab qoladi
io.use((socket, next) => {
  const token = socket.handshake.auth?.token;
  if (token) {
    socket.data.user = jwt.verify(token, secret);
    // next() yo'q — hech narsa bo'lmaydi!
  }
});

// ✅
io.use((socket, next) => {
  // ... tekshirish
  next(); // ← har doim chaqirilishi kerak
});

2. socket.handshake.query va socket.handshake.auth aralashtirib yuborish

// ❌ — URL da token: ws://localhost:4000?token=abc
socket.handshake.query.token // ← URL da ko'rinadi, log larda ham

// ✅ — auth obektida
socket.handshake.auth.token // ← xavfsizroq, URL da ko'rinmaydi

3. Xona ruxsatini tekshirmaslik

// ❌ — har kim istalgan xonaga kirishi mumkin
socket.on('xonaga-kir', (xonaId) => {
  socket.join(xonaId); // ← ruxsatsiz
});

// ✅
socket.on('xonaga-kir', async (xonaId) => {
  const ruxsat = await tekshirRuxsat(socket.data.user.id, xonaId);
  if (!ruxsat) return socket.emit('xato', { xabar: 'Ruxsat yo\'q' });
  socket.join(xonaId);
});

Xulosa

  • Socket.io middleware — ulanish bir marta tekshiriladi; socket.data da saqlanadi
  • socket.handshake.auth.token — JWT yuborish uchun xavfsiz yo'l
  • next(new Error(...)) — ulanishni rad etish; mijoz connect_error eventida qabul qiladi
  • Namespace middleware — faqat o'sha namespace ga tegishli
  • Xona ruxsatini socket.on('xonaga-kir') ichida alohida tekshiring

Keyingi dars: 45-dars: Server-Sent Events — bir tomonlama real-time