Login / Signup endpointlari
Bu darsda autentifikatsiyaning ikki asosiy endpointini yozamiz:
POST /auth/signup— yangi foydalanuvchini ro‘yxatdan o‘tkazish;POST /auth/login— email va parolni tekshirish.
Kod birinchi qarashda oddiy ko‘rinadi: ma’lumotni olish, bazaga yozish, keyin solishtirish. Lekin har bir qadamda xavfsizlikka oid nozik jihatlar bor. Ularni birma-bir ko‘rib chiqamiz.
Login muvaffaqiyatli bo‘lganda klientga token berilishi kerak. Token yaratish keyingi darsda qo‘shiladi. Hozircha login faqat foydalanuvchi ma’lumotini qaytaradi.
Foydalanuvchi modeli
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'reader',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
type User struct {
ID int64
Email string
Name string
PasswordHash string
Role string
CreatedAt time.Time
}
User — ichki model. U hech qachon to‘g‘ridan-to‘g‘ri JSON’ga aylantirilmaydi. Klientga alohida UserResponse DTO’si qaytariladi. Sababi Response struct formatlash darsida tushuntirilgan.
Bu darsda kodni bazasiz ishga tushirish uchun foydalanuvchilar xotirada saqlanadi. Dars oxirida PostgreSQL uchun mos repository metodlari ham keltirilgan.
1. Email’ni normallashtirish
[email protected], [email protected] va [email protected] — bitta email. Ular turli yozuv sifatida saqlansa, bitta odam uchta akkaunt ochishi mumkin, login esa faqat ro‘yxatdan o‘tishda qanday yozilgan bo‘lsa, shunday ishlaydi.
Email ham ro‘yxatdan o‘tishda, ham login paytida normallashtiriladi. Normallashtirish validatsiyadan oldin bajariladi. Aks holda email qoidasi " [email protected] " ko‘rinishidagi qiymatni bo‘shliqlar sababli rad etadi.
2. Ro‘yxatdan o‘tish
type signupRequest struct {
Email string `json:"email" validate:"required,email,max=254"`
Name string `json:"name" validate:"required,min=2,max=100"`
Password string `json:"password" validate:"required,min=8,max=72"`
}
Signup handler qadamlari:
- So‘rov tanasini o‘qish va validatsiya qilish.
- Email’ni normallashtirish.
- Parolni
bcryptbilan xeshlash. - Foydalanuvchini saqlash. Email band bo‘lsa —
409 Conflict. 201 Createdva foydalanuvchi ma’lumotini qaytarish. Parol va xesh qaytarilmaydi.
Warning
role maydoni so‘rovdan o‘qilmaydi. Aks holda istalgan odam "role": "admin" yuborib, o‘zini administrator qilib oladi. Bunday zaiflik mass assignment deb ataladi. Yangi foydalanuvchi har doim eng past huquqli rolni oladi. Rolni faqat administrator alohida endpoint orqali o‘zgartiradi.
Email band ekanini aytish kerakmi?
"Bu email allaqachon ro‘yxatdan o‘tgan" degan xabar qulay, lekin u ma’lumot sizdiradi. Hujumchi signup endpointi orqali qaysi emaillar tizimda borligini tekshirib chiqishi mumkin. Bu hujum user enumeration (foydalanuvchilarni sanab chiqish) deyiladi.
Ikki yondashuv bor:
- Qulaylik birinchi o‘rinda.
409 Conflictqaytariladi. Aksariyat ilovalar shunday qiladi. Qo‘shimcha himoya sifatida signup so‘rovlari soni cheklanadi. - Maxfiylik birinchi o‘rinda. Har doim bir xil javob qaytadi: "Emailingizga xat yubordik". Email yangi bo‘lsa, tasdiqlash havolasi yuboriladi. Band bo‘lsa, "Sizda allaqachon akkaunt bor" degan xat boradi. Bu usul tibbiyot yoki moliya kabi nozik sohalarda ishlatiladi.
Bu darsda birinchi yondashuv ishlatiladi. Login’da esa ma’lumot sizib chiqmasligi majburiy.
3. Tizimga kirish
type loginRequest struct {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
}
Login’dagi validatsiya signup’dagidan yumshoqroq. Unda min=8 qoidasi yo‘q. Nima uchun? Parol talablari vaqt o‘tishi bilan kuchaytirilishi mumkin. Eski, qisqaroq parolli foydalanuvchilar ham tizimga kira olishi kerak.
Bir xil xato xabari
// ❌ Ma’lumot sizdiradi
if user == nil {
return "bunday email topilmadi"
}
if !checkPassword(...) {
return "parol noto‘g‘ri"
}
// ✅ Bir xil javob
return "email yoki parol noto‘g‘ri"
Birinchi variantda hujumchi xabarga qarab email mavjudligini biladi. Ikkinchisida esa ikkala holat bir xil ko‘rinadi.
Vaqt bo‘yicha hujum (timing attack)
Xabar bir xil bo‘lsa ham, javob vaqti ma’lumot sizdirishi mumkin:
Hujumchi javob vaqtini o‘lchab, qaysi emaillar mavjudligini baribir aniqlaydi. Himoya usuli — foydalanuvchi topilmasa ham bcrypt tekshiruvini bajarish. Buning uchun oldindan tayyorlangan soxta xesh ishlatiladi:
var dummyHash, _ = bcrypt.GenerateFromPassword([]byte("dummy-password"), bcryptCost)
user, err := users.ByEmail(ctx, email)
if err != nil {
// Foydalanuvchi yo‘q, lekin vaqt baribir sarflanadi
bcrypt.CompareHashAndPassword(dummyHash, []byte(password))
return ErrInvalidCredentials
}
if bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)) != nil {
return ErrInvalidCredentials
}
Endi ikkala holat taxminan bir xil vaqt oladi.
To‘liq kod
| main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | |
Sinab ko‘rish
curl -X POST http://localhost:8080/auth/signup \
-d '{"email":" [email protected] ","name":"Aziza","password":"juda-maxfiy-parol"}'
{"data":{"id":1,"email":"[email protected]","name":"Aziza","role":"reader","created_at":"2026-09-30T10:00:00.123456Z"}}
Email kichik harflarga o‘tkazildi va bo‘shliqlar olib tashlandi. Javobda parol ham, xesh ham yo‘q.
Rolni o‘zgartirishga urinamiz:
curl -X POST http://localhost:8080/auth/signup \
-d '{"email":"[email protected]","name":"X","password":"12345678","role":"admin"}'
DisallowUnknownFields tufayli notanish role maydoni rad etildi. Bu himoyaning qo‘shimcha qatlami. Asosiy himoya esa service’da: rol har doim reader qilib belgilanadi.
Takroriy ro‘yxatdan o‘tish:
curl -X POST http://localhost:8080/auth/signup \
-d '{"email":"[email protected]","name":"Aziza 2","password":"boshqa-parol-123"}'
Login:
curl -X POST http://localhost:8080/auth/login -d '{"email":"[email protected]","password":"juda-maxfiy-parol"}'
curl -X POST http://localhost:8080/auth/login -d '{"email":"[email protected]","password":"notogri"}'
curl -X POST http://localhost:8080/auth/login -d '{"email":"[email protected]","password":"notogri"}'
{"data":{"id":1,"email":"[email protected]","name":"Aziza","role":"reader","created_at":"2026-09-30T10:00:00.123456Z"}}
{"error":{"code":"invalid_credentials","message":"email yoki parol noto‘g‘ri"}}
{"error":{"code":"invalid_credentials","message":"email yoki parol noto‘g‘ri"}}
Oxirgi ikki javob bir xil. Hujumchi [email protected] tizimda yo‘qligini javobdan ham, javob vaqtidan ham bila olmaydi.
Vaqtni tekshirib ko‘ring:
curl -o /dev/null -s -w "%{time_total}\n" -X POST http://localhost:8080/auth/login \
-d '{"email":"[email protected]","password":"notogri"}'
curl -o /dev/null -s -w "%{time_total}\n" -X POST http://localhost:8080/auth/login \
-d '{"email":"[email protected]","password":"notogri"}'
PostgreSQL uchun repository
Xotiradagi repository’ni bazaga ulangan versiyaga almashtirish uchun faqat ikkita metod yoziladi:
func (r *PostgresUserRepo) Create(ctx context.Context, u User) (User, error) {
err := r.pool.QueryRow(ctx,
`INSERT INTO users (email, name, password_hash, role)
VALUES ($1, $2, $3, $4)
RETURNING id, created_at`,
u.Email, u.Name, u.PasswordHash, u.Role,
).Scan(&u.ID, &u.CreatedAt)
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
return User{}, ErrEmailTaken
}
return u, err
}
func (r *PostgresUserRepo) ByEmail(ctx context.Context, email string) (User, error) {
var u User
err := r.pool.QueryRow(ctx,
`SELECT id, email, name, password_hash, role, created_at FROM users WHERE email = $1`,
email,
).Scan(&u.ID, &u.Email, &u.Name, &u.PasswordHash, &u.Role, &u.CreatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return User{}, ErrNotFound
}
return u, err
}
Email bandligini oldindan SELECT bilan tekshirish o‘rniga to‘g‘ridan-to‘g‘ri INSERT qilinadi. Takrorlanish UNIQUE cheklovi orqali aniqlanadi. Bu usul bir vaqtda kelgan ikki so‘rovda ham to‘g‘ri ishlaydi. Tafsilotlar PostgreSQL bilan bog‘lanish darsida.
Xulosa
- Email ro‘yxatdan o‘tishda ham, login’da ham normallashtiriladi.
- Parol faqat
bcryptxeshi ko‘rinishida saqlanadi. Javobda parol ham, xesh ham qaytmaydi. - Rol va boshqa imtiyozli maydonlar so‘rovdan olinmaydi.
- Login har qanday muvaffaqiyatsizlikda bitta umumiy xabar qaytaradi.
- Foydalanuvchi topilmasa ham
bcrypttekshiruvi bajariladi. Shunda javob vaqti ma’lumot sizdirmaydi. - Email noyobligi baza darajasida
UNIQUEcheklovi bilan kafolatlanadi.
Keyingi darsda login muvaffaqiyatli bo‘lganda JWT access token yaratish va uni tekshirishni o‘rganamiz.