Tarkibga o'tish

Session va Token-based auth farqi

Oldingi darsda HTTP holatsiz ekanini aytgan edik. Login’dan keyin server klientga maxsus belgi beradi, klient esa uni har bir so‘rovda qaytarib yuboradi. Bu belgini tashkil qilishning ikki asosiy usuli bor: session va token.

Ikki xil mehmonxonani tasavvur qiling.

Birinchisida qabulxona sizga faqat raqamli kalit beradi, masalan 305. Kim qaysi xonada yashashi, qachon kelgani va qachon ketishi qabulxona daftarida yozilgan. Kalit yo‘qolsa, qabulxona uni daftardan o‘chirib qo‘yadi va kalit darhol ishlamay qoladi. Bu — session.

Ikkinchisida sizga muhrlangan guvohnoma beriladi: "Ali Valiyev, 305-xona, 5-oktabrgacha. Direktor imzosi". Qo‘riqchi daftarga qaramaydi. U faqat imzo haqiqiy va muddat tugamaganini tekshiradi. Bu — token.

Session asosidagi autentifikatsiya

1. POST /login {email, password}
       Server: parol to‘g‘ri
       Server: session yaratadi → sessions["f3a9..."] = {user_id: 42, expires: ...}
   ← Set-Cookie: session_id=f3a9...; HttpOnly; Secure; SameSite=Lax

2. GET /me
   Cookie: session_id=f3a9...
       Server: sessions["f3a9..."] → user_id 42
   ← 200 {"id": 42, ...}

3. POST /logout
       Server: delete(sessions, "f3a9...")

Session ID — hech qanday ma’noga ega bo‘lmagan uzun tasodifiy satr. Barcha ma’lumot serverda saqlanadi: xotirada, Redis’da yoki bazada. Klient faqat "kalit raqami"ni saqlaydi.

Go’da oddiy session

main.go
package main

import (
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"
)

type session struct {
    userID  int64
    expires time.Time
}

type sessionStore struct {
    mu       sync.Mutex
    sessions map[string]session
}

func (s *sessionStore) create(userID int64, ttl time.Duration) string {
    b := make([]byte, 32)
    rand.Read(b)
    id := base64.RawURLEncoding.EncodeToString(b)

    s.mu.Lock()
    defer s.mu.Unlock()
    s.sessions[id] = session{userID: userID, expires: time.Now().Add(ttl)}
    return id
}

func (s *sessionStore) get(id string) (session, bool) {
    s.mu.Lock()
    defer s.mu.Unlock()
    sess, ok := s.sessions[id]
    if !ok || time.Now().After(sess.expires) {
        delete(s.sessions, id)
        return session{}, false
    }
    return sess, true
}

func (s *sessionStore) delete(id string) {
    s.mu.Lock()
    defer s.mu.Unlock()
    delete(s.sessions, id)
}

func main() {
    store := &sessionStore{sessions: make(map[string]session)}
    const ttl = 24 * time.Hour

    mux := http.NewServeMux()

    mux.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) {
        // Soddalik uchun parol tekshiruvi tushirib qoldirildi
        id := store.create(42, ttl)
        http.SetCookie(w, &http.Cookie{
            Name:     "session_id",
            Value:    id,
            Path:     "/",
            MaxAge:   int(ttl.Seconds()),
            HttpOnly: true,
            Secure:   true,
            SameSite: http.SameSiteLaxMode,
        })
        fmt.Fprintln(w, "Xush kelibsiz!")
    })

    mux.HandleFunc("GET /me", func(w http.ResponseWriter, r *http.Request) {
        c, err := r.Cookie("session_id")
        if err != nil {
            http.Error(w, "tizimga kiring", http.StatusUnauthorized)
            return
        }
        sess, ok := store.get(c.Value)
        if !ok {
            http.Error(w, "session muddati tugagan", http.StatusUnauthorized)
            return
        }
        fmt.Fprintf(w, "Siz %d-foydalanuvchisiz\n", sess.userID)
    })

    mux.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Request) {
        if c, err := r.Cookie("session_id"); err == nil {
            store.delete(c.Value)
        }
        http.SetCookie(w, &http.Cookie{Name: "session_id", Path: "/", MaxAge: -1})
        fmt.Fprintln(w, "Tizimdan chiqdingiz")
    })

    log.Fatal(http.ListenAndServe(":8080", mux))
}
curl -c cookies.txt -X POST http://localhost:8080/login
curl -b cookies.txt http://localhost:8080/me
curl -b cookies.txt -X POST http://localhost:8080/logout
curl -b cookies.txt http://localhost:8080/me
Xush kelibsiz!
Siz 42-foydalanuvchisiz
Tizimdan chiqdingiz
session muddati tugagan

curl -c cookie’ni faylga saqlaydi, -b esa fayldan o‘qib yuboradi. Brauzer buni avtomatik bajaradi.

Info

Cookie’da Secure: true o‘rnatilgan, ya’ni brauzer uni faqat HTTPS orqali yuboradi. curl va zamonaviy brauzerlar localhost uchun istisno qiladi. Shuning uchun misol lokal kompyuterda HTTP orqali ham ishlaydi.

Session ID qanday bo‘lishi kerak?

  • Tasodifiy va uzun. crypto/rand bilan kamida 128 bit, misolda esa 256 bit. math/rand yoki ketma-ket raqamlar ishlatilmaydi, aks holda ularni taxmin qilish mumkin.
  • Ma’nosiz. ID ichida foydalanuvchi ID’si yoki email bo‘lmaydi.
  • Login’dan keyin yangilanadi. Login’dan oldin berilgan ID’ni qayta ishlatish session fixation hujumiga yo‘l ochadi.
Parametr Ma’nosi Nima uchun kerak
HttpOnly JavaScript cookie’ni o‘qiy olmaydi XSS orqali cookie o‘g‘irlanishining oldini oladi
Secure Faqat HTTPS orqali yuboriladi Tarmoqda ushlab olinishining oldini oladi
SameSite=Lax Boshqa saytdan kelgan POST so‘rovlarga cookie qo‘shilmaydi CSRF hujumlaridan himoyalaydi
SameSite=Strict Boshqa saytdan kelgan har qanday so‘rovga qo‘shilmaydi Kuchliroq himoya, lekin tashqi havolalardan kelganda foydalanuvchi "tizimdan chiqqan" bo‘lib ko‘rinadi
Path=/ Butun saytga tegishli
MaxAge Necha soniya yashaydi -1 — darhol o‘chirish

Token asosidagi autentifikatsiya

1. POST /login {email, password}
       Server: parol to‘g‘ri
       Server: token yaratadi va imzolaydi → {"sub": 42, "exp": ...} + imzo
   ← 200 {"access_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjQyfQ.x7k..."}

2. GET /me
   Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjQyfQ.x7k...
       Server: imzoni tekshiradi → to‘g‘ri, sub = 42
   ← 200 {"id": 42, ...}

Token ichida foydalanuvchi haqidagi ma’lumot bor: kim ekani, muddati qachon tugashi. Server hech narsani saqlamaydi. U faqat tokenni o‘zi imzolaganini tekshiradi. Imzoni faqat maxfiy kalitni biladigan server yarata oladi. Shuning uchun token soxtalashtirilsa, bu darhol aniqlanadi.

Eng keng tarqalgan token formati — JWT (JSON Web Token). Uning tuzilishi JWT yaratish va tekshirish darsida ko‘riladi.

Solishtirish

Mezon Session Token (JWT)
Ma’lumot qayerda Serverda Tokenning o‘zida
Har bir so‘rovda Omborga (Redis, baza) murojaat Faqat imzo tekshiriladi
Masshtablash Barcha server nusxalari umumiy omborga ulanadi Har bir nusxa mustaqil tekshiradi
Chiqish (logout) Session o‘chiriladi, darhol ishlamay qoladi Token muddati tugaguncha ishlaydi
Bloklash Barcha session’larni o‘chirish kifoya Qo‘shimcha mexanizm kerak
Hajmi ~40 bayt Bir necha yuz bayt
Mobil ilova va serverlararo Mumkin, lekin noqulay Qulay
Brauzer Cookie orqali tabiiy ishlaydi Saqlash joyini tanlash kerak

Tokenning asosiy muammosi: bekor qilish

Foydalanuvchi "tizimdan chiqish" tugmasini bosdi yoki telefoni o‘g‘irlandi. Session bo‘lsa, uni serverda o‘chirish kifoya. JWT esa muddati tugaguncha haqiqiy qoladi, chunki server uni hech qayerda saqlamaydi.

Bu muammo odatda ikki usul bilan hal qilinadi:

1. Qisqa muddatli access token va uzoq muddatli refresh token.

access_token   → 15 daqiqa, har bir so‘rovda yuboriladi
refresh_token  → 30 kun, faqat POST /auth/refresh uchun, serverda saqlanadi

Access token o‘g‘irlansa, u ko‘pi bilan 15 daqiqa ishlaydi. Refresh token esa serverda saqlanadi, shu sababli uni istalgan paytda bekor qilish mumkin. Access token muddati tugaganda klient refresh token bilan yangisini oladi.

Refresh token amalda session’ga o‘xshaydi. Ya’ni bu usul ikkala yondashuvning afzalliklarini birlashtiradi: tez-tez bo‘ladigan so‘rovlar omborga murojaat qilmaydi, lekin sessiyani bekor qilish imkoni ham saqlanadi.

2. Qora ro‘yxat (denylist). Bekor qilingan tokenlarning ID’lari (jti) muddati tugaguncha Redis’da saqlanadi. Har bir so‘rovda token shu ro‘yxatda yo‘qligi tekshiriladi. Bu holatda token holatsizlik afzalligini qisman yo‘qotadi.

Brauzerda tokenni qayerda saqlash kerak?

Joy XSS xavfi CSRF xavfi
localStorage ⚠️ Istalgan JavaScript o‘qiy oladi Yo‘q
JavaScript xotirasi (o‘zgaruvchi) Kamroq, sahifa yangilansa yo‘qoladi Yo‘q
HttpOnly cookie JavaScript o‘qiy olmaydi ⚠️ SameSite va CSRF himoyasi kerak

Aniq bir "to‘g‘ri" javob yo‘q. Ko‘p hollarda qo‘llaniladigan kelishuv:

  • Brauzer ilovasi (SPA) va API bitta domenda bo‘lsa — HttpOnly, Secure, SameSite cookie. Bu amalda session yondashuviga yaqin.
  • Mobil ilova yoki boshqa server — Authorization: Bearer sarlavhasi. Token qurilmaning xavfsiz omborida saqlanadi: iOS Keychain yoki Android Keystore.

Qaysi birini tanlash kerak?

Session tanlang, agar:

  • asosiy klient brauzer va API bitta domenda bo‘lsa;
  • foydalanuvchini darhol tizimdan chiqarish imkoni muhim bo‘lsa, masalan bank yoki admin panelda;
  • sizda allaqachon Redis yoki boshqa umumiy ombor bo‘lsa.

Token tanlang, agar:

  • klientlar mobil ilovalar, boshqa servislar yoki uchinchi tomon dasturlari bo‘lsa;
  • bir nechta mustaqil servis foydalanuvchini umumiy ombor bo‘lmagan holda tanishi kerak bo‘lsa;
  • mikroservislar orasida foydalanuvchi ma’lumotini uzatish kerak bo‘lsa.

Bu kursda token yondashuvini o‘rganamiz. Chunki u API’lar orasida keng tarqalgan va unda ichki jarayonlarni tushunish uchun ko‘proq narsa bor. Keyingi darslardagi auth middleware va ruxsat tekshiruvi mantiqi esa session bilan ham deyarli o‘zgarmaydi.

Xulosa

  • Session’da ma’lumot serverda, klientda faqat tasodifiy ID bo‘ladi. Uni darhol bekor qilish oson.
  • Token’da ma’lumot tokenning o‘zida, server faqat imzoni tekshiradi. U masshtablash uchun qulay, lekin bekor qilish qiyinroq.
  • Cookie uchun HttpOnly, Secure va SameSite parametrlari majburiy.
  • Tokenni bekor qilish muammosi qisqa muddatli access token va serverda saqlanadigan refresh token orqali hal qilinadi.
  • Tanlov klient turiga va xavfsizlik talablariga bog‘liq.

Keyingi darsda ro‘yxatdan o‘tish va login endpointlarini yozamiz.