JSON Web Token (JWT) adalah standar terbuka untuk merepresentasikan klaim (claims) secara aman antara dua pihak dalam bentuk JSON yang ditandatangani secara digital, menjadi metode autentikasi paling umum dipakai pada REST API modern karena sifatnya yang stateless dan tidak membutuhkan session di sisi server.

JWT terdiri dari tiga bagian yang dipisahkan titik: header (algoritma signing), payload (data klaim seperti user id dan role), dan signature (tanda tangan digital yang memverifikasi token tidak diubah), semuanya di-encode dengan Base64 dan digabung menjadi satu string token.

Artikel ini membahas cara mengonfigurasi autentikasi JWT di ASP.NET Core Web API, mulai dari menghasilkan token saat login berhasil, memvalidasi token pada setiap request, hingga melindungi endpoint tertentu agar hanya bisa diakses pengguna yang sudah terautentikasi.

Menghasilkan JWT Saat Login Berhasil

Token dihasilkan dengan claims, signing key, dan masa berlaku

var claims = new[] { new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Role, user.Role) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken(issuer, audience, claims, expires: DateTime.Now.AddHours(1), signingCredentials: creds); string tokenString = new JwtSecurityTokenHandler().WriteToken(token);
Setelah kredensial pengguna berhasil diverifikasi, server menghasilkan JWT berisi klaim seperti nama pengguna dan role, ditandatangani dengan secret key yang hanya diketahui server, dan diberi masa berlaku tertentu. Token ini kemudian dikirim ke client untuk disertakan pada setiap request berikutnya sebagai bukti identitas.

Mengonfigurasi Validasi JWT di Middleware

Konfigurasi validasi memastikan token asli dan belum kedaluwarsa

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)) }; });
Konfigurasi TokenValidationParameters menentukan aspek apa saja yang diperiksa saat memvalidasi token masuk: apakah issuer dan audience sesuai yang diharapkan, apakah token belum kedaluwarsa (ValidateLifetime), dan yang terpenting, apakah signature token cocok dengan secret key server (ValidateIssuerSigningKey), memastikan token tidak dipalsukan.

Melindungi Endpoint dengan Atribut Authorize

Atribut Authorize memeriksa token sebelum action dijalankan

[Authorize] [HttpGet("profil")] public IActionResult GetProfil() { string username = User.Identity.Name; return Ok($"Profil untuk {username}"); } // Request tanpa token valid akan menerima 401 Unauthorized // SEBELUM action method ini sempat dijalankan
Atribut [Authorize] pada action method atau controller memastikan hanya request dengan token JWT yang valid yang bisa mengakses endpoint tersebut, dengan middleware autentikasi menangani pemeriksaan ini secara otomatis sebelum kode di dalam action method sempat dijalankan sama sekali.

Mengirim Token pada Request Berikutnya

Token dikirim lewat header Authorization dengan skema Bearer

// Client menyertakan token di header Authorization // pada setiap request ke endpoint yang terproteksi: GET /api/produk/profil HTTP/1.1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Client yang sudah menerima token dari endpoint login wajib menyertakannya pada header Authorization dengan format 'Bearer {token}' di setiap request ke endpoint yang terproteksi, dan middleware JWT di server akan otomatis mengekstrak serta memvalidasi token tersebut dari header ini.

Refresh Token: Mengatasi Access Token JWT yang Masa Berlakunya Pendek

Rotasi refresh token: token lama dicabut setiap kali dipakai untuk refresh

public class AuthService { public async Task<TokenResult> RefreshAsync(string refreshToken) { var stored = await _db.RefreshTokens .FirstOrDefaultAsync(r => r.Token == refreshToken && !r.IsRevoked); if (stored is null || stored.ExpiresAt < DateTime.UtcNow) throw new UnauthorizedAccessException("Refresh token tidak valid atau kedaluwarsa."); stored.IsRevoked = true; // rotasi: token lama langsung dicabut var newAccessToken = _jwtGenerator.GenerateAccessToken(stored.UserId); var newRefreshToken = _jwtGenerator.GenerateRefreshToken(stored.UserId); await _db.SaveChangesAsync(); return new TokenResult(newAccessToken, newRefreshToken); // Access token baru berlaku 15 menit, refresh token baru berlaku 7 hari } }

Access token JWT sengaja dibuat berumur pendek, biasanya 15 hingga 60 menit, agar dampak kebocoran token tetap terbatas waktu. Namun masa berlaku sependek itu tidak praktis jika user harus login ulang setiap kali token kedaluwarsa, sehingga pola refresh token dipakai: sebuah token kedua yang masa berlakunya jauh lebih panjang (misalnya 7 hari) dan disimpan di database, dipakai khusus untuk meminta access token baru tanpa perlu memasukkan kredensial ulang.

Praktik keamanan yang penting pada implementasi refresh token adalah token rotation, yaitu setiap kali refresh token dipakai untuk mendapatkan access token baru, refresh token lama langsung dicabut (IsRevoked = true) dan refresh token baru diterbitkan. Dengan pola ini, jika refresh token yang sudah dicabut tiba-tiba dipakai lagi, sistem bisa mendeteksi indikasi pencurian token dan mencabut seluruh sesi milik user tersebut sebagai langkah pengamanan.

Refresh token sebaiknya disimpan di database (bukan hanya di JWT itu sendiri) justru karena JWT access token bersifat stateless dan tidak bisa dicabut sebelum masa berlakunya habis; dengan menjadikan refresh token sebagai record di database yang punya status IsRevoked, tim bisa memaksa logout seluruh sesi seorang user kapan saja, sesuatu yang tidak mungkin dilakukan hanya dengan mengandalkan access token JWT yang murni self-contained.

Claims-Based Identity: Menyimpan dan Membaca Data User dari Token

ClaimsPrincipal (User) di controller terisi otomatis dari payload JWT yang tervalidasi

// Membuat claims saat JWT diterbitkan var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Email, user.Email), new Claim("Departemen", user.Departemen), new Claim("JobLevel", user.JobLevel.ToString()) }; var token = new JwtSecurityToken(claims: claims, expires: DateTime.UtcNow.AddMinutes(15), ...); // Membaca claims di controller tanpa query ulang ke database [HttpGet("profil-saya")] [Authorize] public IActionResult GetProfilSaya() { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var departemen = User.FindFirstValue("Departemen"); return Ok(new { userId, departemen }); } // Data ini diambil langsung dari token, tanpa hit database sama sekali

Setiap JWT yang berhasil divalidasi oleh middleware autentikasi secara otomatis diubah menjadi objek ClaimsPrincipal yang tersedia lewat properti User di dalam controller, berisi seluruh claim yang disisipkan saat token diterbitkan. Pola ini memungkinkan informasi dasar tentang user yang sedang login, seperti id, email, atau departemen, diambil langsung dari token tanpa perlu melakukan query tambahan ke database pada setiap request yang membutuhkannya. Pendekatan ini mengurangi beban database secara signifikan pada endpoint yang sering diakses dan hanya membutuhkan identitas dasar user.

Claim kustom seperti "Departemen" atau "JobLevel" pada contoh di atas ditambahkan secara manual saat proses pembuatan token di AuthService, dan nilainya tidak akan berubah sampai user tersebut login ulang dan mendapatkan token baru. Konsekuensinya, data yang disimpan sebagai claim sebaiknya berupa informasi yang jarang berubah dalam rentang waktu masa berlaku token, karena perubahan departemen seorang user tidak akan langsung tercermin pada token lama yang masih aktif. Informasi yang sering berubah seperti saldo atau status langganan sebaiknya tetap diambil dari database, bukan disimpan sebagai claim pada token.

Untuk mempermudah pembacaan claim yang berulang di banyak controller, praktik yang baik adalah membuat extension method pada ClaimsPrincipal, misalnya User.GetUserId() atau User.GetDepartemen(), sehingga logika pengambilan claim terpusat di satu tempat dan tidak perlu mengetik string literal nama claim secara berulang di setiap endpoint yang membutuhkannya, sekaligus mengurangi risiko salah ketik nama claim antar file. Extension method ini juga memudahkan perubahan nama claim di masa depan karena cukup diubah di satu tempat saja.

Cookie Authentication vs JWT: Memilih Sesuai Jenis Klien

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Account/Login"; options.ExpireTimeSpan = TimeSpan.FromHours(8); }); // Cocok untuk aplikasi MVC/Razor Pages dengan sesi browser, // server menyimpan state login lewat cookie terenkripsi

Cookie authentication mengandalkan browser untuk menyimpan dan mengirim cookie secara otomatis pada setiap request yang dikirim ke server, sehingga sangat cocok untuk aplikasi web tradisional seperti Razor Pages atau MVC yang seluruhnya dirender di sisi server. Proses logout dan invalidasi sesi juga menjadi lebih sederhana karena server bisa langsung menghapus cookie atau menandainya sebagai tidak valid tanpa perlu mekanisme tambahan di sisi klien.

JWT jauh lebih cocok dipakai untuk API yang dikonsumsi oleh klien non-browser seperti aplikasi mobile atau layanan lain yang berkomunikasi langsung lewat HTTP, karena token dikirim secara eksplisit lewat header Authorization dan tidak bergantung pada mekanisme cookie bawaan browser. Memilih salah satu dari keduanya sebaiknya mengikuti jenis klien utama yang dilayani aplikasi, bukan dipaksakan memakai JWT untuk aplikasi web yang sebenarnya cukup terlayani dengan sesi cookie biasa.

Kesalahan Umum yang Sering Terjadi

Menyimpan Secret Key JWT Langsung di Kode Sumber

Menuliskan secret key untuk signing JWT langsung di dalam kode yang ter-commit ke version control adalah risiko keamanan besar; secret key sebaiknya disimpan di User Secrets untuk development dan secret manager atau environment variable untuk production.

Memberi Masa Berlaku Token yang Terlalu Lama

Token dengan masa berlaku terlalu panjang meningkatkan risiko jika token tersebut bocor atau dicuri, karena token JWT yang sudah diterbitkan tidak bisa langsung dibatalkan tanpa mekanisme tambahan seperti token blacklist atau refresh token.

Menyimpan Data Sensitif di Payload JWT

Payload JWT hanya di-encode dengan Base64, bukan dienkripsi, sehingga siapa pun yang memiliki token bisa membaca isi payload-nya; data sensitif seperti password tidak boleh pernah dimasukkan ke dalam klaim JWT.

Kapan Pendekatan Ini Tidak Cocok Dipakai

JWT memberikan autentikasi yang stateless dan skalabel karena server tidak perlu menyimpan session, sangat cocok untuk arsitektur microservices dan API yang diakses dari berbagai jenis client.

Namun sifat stateless ini juga menjadi kekurangan karena token yang sudah diterbitkan sulit dibatalkan sebelum masa berlakunya habis, sehingga aplikasi yang butuh kemampuan logout paksa atau pencabutan akses instan perlu menambahkan mekanisme tambahan seperti refresh token dengan daftar token yang dicabut.

Ringkasan

Autentikasi JWT menjadi standar de facto untuk REST API modern berkat sifatnya yang stateless dan mudah diintegrasikan lintas platform. Pahami cara kerja signing dan validasi token, lindungi endpoint dengan atribut Authorize, dan selalu jaga keamanan secret key yang dipakai untuk menandatangani token tersebut.

Seri Belajar .NET Core:

← Sebelumnya: Repository Pattern di ASP.NET Core

→ Selanjutnya: Authorization dan Role Based Access Control (segera terbit)