JWT o'rnatish — JwtBearer, token yaratish, sozlash

~25 daq · .NET Backend · Autentifikatsiya va Avtorizatsiya

Muammodan boshlaylik

JWT nima ekanini bilamiz (59-dars). Endi amalda kerak:

  1. Login da token yaratish va qaytarish
  2. Har bir so'rovda Authorization: Bearer ... sarlavhasini o'qish, imzo va muddatni tekshirish
  3. Controller da joriy foydalanuvchini olish

Buni qo'lda yozish — xavfli (algoritm, muddat, kalit tekshiruvidagi bitta xato — butun tizim ochiq). ASP.NET Core da tayyor middleware bor.


Hayotiy o'xshatma

Ofis binosi. Qabulxona (login endpoint) shaxsni tekshirib, magnit karta (token) beradi. Har bir eshikdagi o'quvchi (JwtBearer middleware) kartani tekshiradi: haqiqiymi, muddati o'tmaganmi. Eshik ortidagi xodim (controller) kartani o'zi tekshirmaydi — o'quvchi o'tkazgan bo'lsa, kim kirganini kartadan biladi.


1. Paket va sozlamalar

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
// appsettings.json
{
  "Jwt": {
    "Issuer": "dokon-api",
    "Audience": "dokon-app",
    "AccessTokenDaqiqa": 15
  }
}

Kalit — appsettings.json da emas, maxfiy joyda (82-dars):

dotnet user-secrets set "Jwt:Kalit" "kamida-32-belgili-uzun-tasodifiy-maxfiy-kalit-qiymati"
public class JwtSozlamalari
{
    public string Issuer { get; set; } = "";
    public string Audience { get; set; } = "";
    public string Kalit { get; set; } = "";
    public int AccessTokenDaqiqa { get; set; } = 15;
}

2. Middleware ni ro'yxatdan o'tkazish

using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var jwt = builder.Configuration.GetSection("Jwt").Get<JwtSozlamalari>()!;
builder.Services.Configure<JwtSozlamalari>(builder.Configuration.GetSection("Jwt"));

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o =>
    {
        o.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = jwt.Issuer,
            ValidateAudience = true,
            ValidAudience = jwt.Audience,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwt.Kalit)),
            ClockSkew = TimeSpan.FromSeconds(30)
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();     // tartib muhim: avval kim ekanini aniqlash
app.UseAuthorization();      // keyin ruxsatni tekshirish

app.MapControllers();

[!INFO] ClockSkew sukut bo'yicha 5 daqiqa — token exp dan keyin yana 5 daqiqa qabul qilinadi (serverlar soatidagi farq uchun). 15 daqiqalik token amalda 20 daqiqa yashaydi. Kichikroq qiymat qo'ying.


3. Token yaratish

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

public interface ITokenService
{
    string AccessTokenYarat(Foydalanuvchi f);
}

public class TokenService(IOptions<JwtSozlamalari> opt) : ITokenService
{
    private readonly JwtSozlamalari _s = opt.Value;

    public string AccessTokenYarat(Foydalanuvchi f)
    {
        var claims = new List<Claim>
        {
            new(JwtRegisteredClaimNames.Sub, f.Id.ToString()),
            new(JwtRegisteredClaimNames.Email, f.Email),
            new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new(ClaimTypes.Role, f.Rol)
        };

        var kalit = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_s.Kalit));
        var imzo = new SigningCredentials(kalit, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _s.Issuer,
            audience: _s.Audience,
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(_s.AccessTokenDaqiqa),
            signingCredentials: imzo);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
builder.Services.AddSingleton<ITokenService, TokenService>();

4. Login endpoint

public record LoginDto(string Email, string Parol);
public record TokenJavob(string AccessToken, int AmalQilishSoniya);

[ApiController]
[Route("api/auth")]
public class AuthController(DokonDbContext db, ITokenService tokens, IOptions<JwtSozlamalari> opt) : ControllerBase
{
    [HttpPost("login")]
    public async Task<ActionResult<TokenJavob>> Login(LoginDto dto, CancellationToken ct)
    {
        var f = await db.Foydalanuvchilar
            .FirstOrDefaultAsync(x => x.Email == dto.Email.ToLowerInvariant(), ct);

        if (f is null || !BCrypt.Net.BCrypt.Verify(dto.Parol, f.ParolXesh))
            return Unauthorized(new { xato = "Email yoki parol noto'g'ri" });

        return new TokenJavob(tokens.AccessTokenYarat(f), opt.Value.AccessTokenDaqiqa * 60);
    }
}
POST /api/auth/login
Content-Type: application/json

{ "email": "lola@mail.uz", "parol": "Kuchli!Parol2026" }
{ "accessToken": "eyJhbGciOiJIUzI1NiIs...", "amalQilishSoniya": 900 }

5. Joriy foydalanuvchini olish

[Authorize]
[HttpGet("men")]
public IActionResult Men()
{
    var id = User.FindFirstValue(ClaimTypes.NameIdentifier);
    var email = User.FindFirstValue(ClaimTypes.Email);
    var rol = User.FindFirstValue(ClaimTypes.Role);
    return Ok(new { id, email, rol });
}

[!WARNING] JwtBearer sukut bo'yicha sub claim ini ClaimTypes.NameIdentifier ga, email ni ClaimTypes.Email ga xaritalaydi (mapping). Shuning uchun User.FindFirstValue("sub") null qaytarishi mumkin. Asl nomlarni saqlash uchun: o.MapInboundClaims = false; — keyin User.FindFirstValue("sub") ishlaydi, lekin rol uchun RoleClaimType = "role" ni ham sozlang.

Qulay kengaytma:

public static class ClaimsKengaytma
{
    public static int FoydalanuvchiId(this ClaimsPrincipal u) =>
        int.Parse(u.FindFirstValue(ClaimTypes.NameIdentifier)
            ?? throw new UnauthorizedAccessException());
}

var meningId = User.FoydalanuvchiId();

Mijoz tomonda

curl https://localhost:5001/api/auth/men \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Token yo'q yoki yaroqsiz — 401 Unauthorized; WWW-Authenticate: Bearer error="invalid_token", error_description="The token expired..." sarlavhasida sabab.

[!TIP] JwtSecurityTokenHandler o'rniga yangi va tezroq JsonWebTokenHandler (Microsoft.IdentityModel.JsonWebTokens) bor — .NET 8+ da JwtBearer ichida sukut bo'yicha ishlatiladi. Token yaratishda ham SecurityTokenDescriptor + JsonWebTokenHandler.CreateToken ishlatish mumkin.


Tez-tez uchraydigan xatolar

UseAuthentication unutilgan yoki UseAuthorization dan keyin — har doim 401.

Kalit 32 baytdan qisqa:

IDX10720: Unable to create KeyedHashAlgorithm for algorithm 'HS256', the key size must be greater than: '256' bits

Issuer/Audience yaratishda va tekshirishda farq qiladi — IDX10214: Audience validation failed.

Kalit kodda yoki git da — kim ko'rsa, istalgan token yasaydi.


Xulosa

AddAuthentication().AddJwtBearer() — TokenValidationParameters: issuer, audience, muddat va imzo kaliti tekshiruvi; ClockSkew ni kamaytiring. Middleware tartibi: UseAuthentication → UseAuthorization.

Token — JwtSecurityToken (claims: sub, email, jti, rol; expires; HmacSha256 imzo) → WriteToken. Login: parolni Verify → token qaytarish. Controller da — User.FindFirstValue(ClaimTypes.NameIdentifier); claim mapping ga e'tibor. Kalit — kamida 32 bayt, maxfiy joyda.


Keyingi dars

61-dars: [Authorize] va [AllowAnonymous] — endpoint himoyasi, policy