Refresh Token — access + refresh juftligi, rotation strategy
Muammodan boshlaylik
Access token 15 daqiqa yashaydi (59-dars) — o'g'irlansa, zarar cheklangan. Lekin foydalanuvchi mobil ilovada har 15 daqiqada parol kiritishi kerakmi? Hech kim bunday ilovani ishlatmaydi.
Tokenni 30 kunlik qilsak — o'g'irlangan token 30 kun ishlaydi, va uni bekor qilib bo'lmaydi (JWT stateless).
Ikkalasini qanday birlashtirish mumkin?
Ikki token
| Access token | Refresh token | |
|---|---|---|
| Muddat | Qisqa — 5–15 daqiqa | Uzoq — 7–30 kun |
| Qayerga yuboriladi | Har bir API so'roviga | Faqat /auth/refresh ga |
| Format | JWT (stateless) | Tasodifiy satr, bazada saqlanadi |
| Bekor qilish | Qiyin | Oson — bazadan o'chirish |
1. Login ──▶ access (15 daq) + refresh (30 kun)
2. API so'rovlar ──▶ Authorization: Bearer <access>
3. Access eskirdi ──▶ 401
4. POST /auth/refresh { refresh } ──▶ yangi access + yangi refresh
5. Chiqish (logout) ──▶ refresh bazadan bekor qilinadi
Hayotiy o'xshatma
Sport zali. Kunlik bilet (access token) — kirishda tekshiriladi, ertaga yaroqsiz. Yillik abonement (refresh token) — seyfda saqlanadi, faqat kassada ko'rsatiladi: har kuni kassada abonementni ko'rsatib yangi kunlik bilet olasiz. Abonement yo'qolsa — kassaga qo'ng'iroq qilib uni bekor qilasiz, va o'g'ri endi bilet ololmaydi.
Bazadagi model
public class RefreshToken
{
public int Id { get; set; }
public string TokenXesh { get; set; } = ""; // tokenning o'zi emas — xeshi
public int FoydalanuvchiId { get; set; }
public Foydalanuvchi Foydalanuvchi { get; set; } = null!;
public DateTime Yaratilgan { get; set; } = DateTime.UtcNow;
public DateTime Tugaydi { get; set; }
public DateTime? BekorQilingan { get; set; }
public string? AlmashtirilganXesh { get; set; } // rotatsiyada keyingi token
public bool Faol => BekorQilingan is null && DateTime.UtcNow < Tugaydi;
}
mb.Entity<RefreshToken>().HasIndex(r => r.TokenXesh).IsUnique();
[!INFO] Refresh token — bazada xesh ko'rinishida saqlanadi (parol kabi). Baza sizib chiqsa, xeshlardan haqiqiy tokenlarni olib bo'lmaydi. Token yuqori entropiyali tasodifiy satr bo'lgani uchun bcrypt shart emas — SHA256 yetarli.
Yaratish
using System.Security.Cryptography;
public static class RefreshYordamchi
{
public static string Yarat() =>
Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
public static string Xesh(string token) =>
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(token)));
}
public record TokenJuftligi(string AccessToken, string RefreshToken);
private async Task<TokenJuftligi> JuftlikBerAsync(Foydalanuvchi f, CancellationToken ct)
{
var refresh = RefreshYordamchi.Yarat();
db.RefreshTokenlar.Add(new RefreshToken
{
TokenXesh = RefreshYordamchi.Xesh(refresh),
FoydalanuvchiId = f.Id,
Tugaydi = DateTime.UtcNow.AddDays(30)
});
await db.SaveChangesAsync(ct);
return new TokenJuftligi(tokens.AccessTokenYarat(f), refresh);
}
Login endi JuftlikBerAsync(f, ct) ni qaytaradi.
Yangilash — rotatsiya bilan
public record RefreshDto(string RefreshToken);
[HttpPost("refresh")]
[AllowAnonymous]
public async Task<ActionResult<TokenJuftligi>> Refresh(RefreshDto dto, CancellationToken ct)
{
var xesh = RefreshYordamchi.Xesh(dto.RefreshToken);
var eski = await db.RefreshTokenlar
.Include(r => r.Foydalanuvchi)
.FirstOrDefaultAsync(r => r.TokenXesh == xesh, ct);
if (eski is null) return Unauthorized();
if (eski.BekorQilingan is not null)
{
// allaqachon ishlatilgan token qayta keldi — o'g'irlik belgisi
await db.RefreshTokenlar
.Where(r => r.FoydalanuvchiId == eski.FoydalanuvchiId && r.BekorQilingan == null)
.ExecuteUpdateAsync(s => s.SetProperty(r => r.BekorQilingan, DateTime.UtcNow), ct);
return Unauthorized();
}
if (!eski.Faol) return Unauthorized();
var yangi = await JuftlikBerAsync(eski.Foydalanuvchi, ct);
eski.BekorQilingan = DateTime.UtcNow;
eski.AlmashtirilganXesh = RefreshYordamchi.Xesh(yangi.RefreshToken);
await db.SaveChangesAsync(ct);
return yangi;
}
Rotatsiya — har bir refresh da yangi refresh token beriladi, eskisi bekor qilinadi. Agar eski (ishlatilgan) token yana kelsa — kimdir uni nusxalagan: foydalanuvchining barcha tokenlari bekor qilinadi, qayta login talab etiladi.
Chiqish
[HttpPost("logout")]
[AllowAnonymous]
public async Task<IActionResult> Logout(RefreshDto dto, CancellationToken ct)
{
var xesh = RefreshYordamchi.Xesh(dto.RefreshToken);
await db.RefreshTokenlar
.Where(r => r.TokenXesh == xesh && r.BekorQilingan == null)
.ExecuteUpdateAsync(s => s.SetProperty(r => r.BekorQilingan, DateTime.UtcNow), ct);
return NoContent();
}
[HttpPost("logout-hammasi")]
[Authorize]
public async Task<IActionResult> HammasidanChiqish(CancellationToken ct)
{
var id = User.FoydalanuvchiId();
await db.RefreshTokenlar
.Where(r => r.FoydalanuvchiId == id && r.BekorQilingan == null)
.ExecuteUpdateAsync(s => s.SetProperty(r => r.BekorQilingan, DateTime.UtcNow), ct);
return NoContent();
}
Parol o'zgarganda ham — barcha refresh tokenlarni bekor qiling.
[!WARNING] Logout dan keyin access token muddati tugaguncha (≤15 daqiqa) ishlashda davom etadi — JWT ni bekor qilib bo'lmaydi. Shuning uchun access token muddati qisqa bo'lishi muhim. Darhol bloklash kerak bo'lsa (masalan, hisob buzilgan) —
jtiqora ro'yxati (Redis da) yoki foydalanuvchida "token versiyasi" claim i.
Mijozda saqlash
- Brauzer — refresh token
httpOnly; Secure; SameSite=Strictcookie da,Path=/api/auth: JavaScript o'qiy olmaydi, faqat refresh endpoint iga yuboriladi - Mobil — Keychain (iOS) / Keystore (Android)
Response.Cookies.Append("refresh", juftlik.RefreshToken, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Path = "/api/auth",
Expires = DateTimeOffset.UtcNow.AddDays(30)
});
Tez-tez uchraydigan xatolar
Refresh token — JWT, bazada yo'q — bekor qilib bo'lmaydi; asosiy foyda yo'qoladi.
Tokenni ochiq saqlash — baza sizsa, hamma sessiyalar o'g'irlanadi; xesh saqlang.
Rotatsiyasiz — o'g'irlangan refresh token 30 kun sezilmay ishlaydi.
Eski tokenlarni tozalamaslik — jadval cheksiz o'sadi; muddati o'tganlarni fon vazifasi bilan o'chiring.
Xulosa
Ikki token: qisqa muddatli access (JWT, har so'rovda) va uzoq muddatli refresh (tasodifiy satr, bazada xesh holida, faqat /auth/refresh ga). Access eskirsa — refresh orqali yangi juftlik.
Rotatsiya: har refresh da yangi token, eskisi bekor; ishlatilgan token qayta kelsa — barcha sessiyalar bekor. Logout — refresh ni bekor qilish (access qisqa muddat yashashda davom etadi). Saqlash — httpOnly cookie yoki mobil xavfsiz omborda.
Keyingi dars
63-dars: Role-based Authorization — Admin, User, Manager rollari