ASP.NET Core API #7: Menambahkan Fitur Login dan Registrasi dengan JWT
Halo lagi, calon penjaga gerbang API!
Sampai saat ini, API kita bisa diakses oleh siapa saja. Semua orang bisa menambah, mengubah, atau menghapus data wisata. Tentu ini berbahaya, seperti membiarkan pintu rumah terbuka lebar. Sekarang kita akan memasang gembok digital yang bernama JWT (JSON Web Token). Dengan JWT, hanya pengguna yang sudah login yang bisa mengakses endpoint tertentu.
Apa itu JWT?
Bayangkan JWT seperti stempel yang diberikan ke pengguna setelah login. Setiap kali pengguna mau masuk ke area terlarang (endpoint tertentu), dia harus menunjukkan stempel itu. Stempel ini sudah ditandatangani oleh server, jadi tidak bisa dipalsukan. Di dalam stempel juga tersimpan data seperti username dan waktu berlaku.
Alur kerjanya:
- Pengguna daftar (registrasi) ke server.
- Pengguna login (mengirim username & password).
- Server memeriksa, kalau cocok, server buatkan JWT dan mengirimkannya ke pengguna.
- Selanjutnya, setiap kali pengguna mau akses endpoint yang diamankan, dia harus menyertakan JWT di header (biasanya
Authorization: Bearer <token>). - Server memverifikasi token, kalau valid, akses diizinkan.
Mari kita implementasikan!
Langkah 1: Install Package yang Diperlukan
Kita perlu beberapa package tambahan untuk JWT dan hashing password. Buka Package Manager Console dan install:
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package BCrypt.Net-Next
Install-Package System.IdentityModel.Tokens.Jwt
Atau lewat NuGet Package Manager. Package BCrypt.Net-Next digunakan untuk meng-hash password (jangan pernah simpan password asli di database!).
Langkah 2: Membuat Model User
Kita perlu tabel untuk menyimpan data pengguna. Buat model baru di folder Models dengan nama User.cs:
using System.ComponentModel.DataAnnotations;
namespace WisataAPI.Models
{
public class User
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Username { get; set; }
[Required]
[EmailAddress]
[MaxLength(100)]
public string Email { get; set; }
[Required]
public string PasswordHash { get; set; } // Yang disimpan adalah hash, bukan password asli
}
}
Perhatikan: kita hanya menyimpan PasswordHash, bukan password asli. Ini penting untuk keamanan.
Tambahkan juga DbSet<User> di WisataDbContext.cs:
public DbSet<User> Users { get; set; }
Sekarang kita perlu membuat migration untuk menambah tabel Users. Buka Package Manager Console, jalankan:
Add-Migration AddUsersTable
Update-Database
Kalau berhasil, tabel Users akan muncul di database.
Langkah 3: Membuat DTO untuk Registrasi dan Login
DTO (Data Transfer Object) digunakan untuk menerima data dari client tanpa mengekspos model asli. Buat folder DTOs, lalu buat dua class:
RegisterDto.cs
using System.ComponentModel.DataAnnotations;
namespace WisataAPI.DTOs
{
public class RegisterDto
{
[Required]
[MaxLength(50)]
public string Username { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[MinLength(6)]
public string Password { get; set; }
}
}
LoginDto.cs
using System.ComponentModel.DataAnnotations;
namespace WisataAPI.DTOs
{
public class LoginDto
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
}
🔧 Langkah 4: Menyiapkan JWT Settings di appsettings.json
Buka appsettings.json, tambahkan bagian Jwt di dalamnya:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost\\SQLEXPRESS;Database=WisataDB;Trusted_Connection=True;TrustServerCertificate=True;"
},
"Jwt": {
"Key": "IniAdalahKunciRahasiaYangSangatPanjangDanSulitDitebak12345!",
"Issuer": "WisataAPI",
"Audience": "WisataAPIClient",
"DurationInMinutes": 60
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Key adalah kunci rahasia untuk menandatangani token. Panjang minimal 32 karakter. Jangan pernah bagikan kunci ini! Di production nanti disimpan di tempat aman (misal environment variable). Issuer dan Audience bisa diisi sesuai nama aplikasi. DurationInMinutes menentukan berapa lama token berlaku.
Langkah 5: Konfigurasi Authentication di Program.cs
Sekarang kita konfigurasi service authentication dengan JWT. Tambahkan kode berikut di Program.cs (setelah AddDbContext atau sebelum AddControllers):
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
// ... setelah builder.Services.AddDbContext...
// Konfigurasi JWT
var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"]);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false; // di production set true
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = jwtSettings["Issuer"],
ValidateAudience = true,
ValidAudience = jwtSettings["Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
Jangan lupa tambahkan juga app.UseAuthentication(); sebelum app.UseAuthorization(); di bagian bawah file (sebelum app.MapControllers();).
Bagian konfigurasi ini memberi tahu ASP.NET untuk menggunakan JWT sebagai mekanisme autentikasi.
Langkah 6: Membuat AuthController
Buat controller baru: AuthController.cs di folder Controllers. Ini akan menangani registrasi dan login.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using WisataAPI.Data;
using WisataAPI.DTOs;
using WisataAPI.Models;
namespace WisataAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly WisataDbContext _context;
private readonly IConfiguration _configuration;
public AuthController(WisataDbContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
}
// POST: api/auth/register
[HttpPost("register")]
public async Task<IActionResult> Register(RegisterDto registerDto)
{
// Cek apakah username sudah dipakai
if (await _context.Users.AnyAsync(u => u.Username == registerDto.Username))
{
return BadRequest("Username sudah digunakan.");
}
// Cek email sudah dipakai
if (await _context.Users.AnyAsync(u => u.Email == registerDto.Email))
{
return BadRequest("Email sudah digunakan.");
}
// Hash password
string passwordHash = BCrypt.Net.BCrypt.HashPassword(registerDto.Password);
// Buat user baru
var user = new User
{
Username = registerDto.Username,
Email = registerDto.Email,
PasswordHash = passwordHash
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
return Ok("Registrasi berhasil.");
}
// POST: api/auth/login
[HttpPost("login")]
public async Task<IActionResult> Login(LoginDto loginDto)
{
// Cari user berdasarkan username
var user = await _context.Users.FirstOrDefaultAsync(u => u.Username == loginDto.Username);
if (user == null)
{
return Unauthorized("Username atau password salah.");
}
// Verifikasi password
bool isPasswordValid = BCrypt.Net.BCrypt.Verify(loginDto.Password, user.PasswordHash);
if (!isPasswordValid)
{
return Unauthorized("Username atau password salah.");
}
// Buat token JWT
var token = GenerateJwtToken(user);
return Ok(new { token });
}
private string GenerateJwtToken(User user)
{
var jwtSettings = _configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email)
}),
Expires = DateTime.UtcNow.AddMinutes(Convert.ToDouble(jwtSettings["DurationInMinutes"])),
Issuer = jwtSettings["Issuer"],
Audience = jwtSettings["Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
}
Penjelasan:
Register: menerima username, email, password. Mengecek duplikat, menghash password dengan BCrypt, simpan user.Login: mencari user berdasarkan username, verifikasi password dengan BCrypt. Jika cocok, buat token JWT dan kembalikan.GenerateJwtToken: membuat token dengan claims (data pengguna) dan kunci rahasia.
Langkah 7: Mengamankan Endpoint dengan [Authorize]
Sekarang kita bisa melindungi endpoint tertentu. Misalnya, kita ingin hanya pengguna yang sudah login yang bisa menambah, mengubah, atau menghapus kategori dan wisata. Caranya, tambahkan atribut [Authorize] di atas controller atau method.
Di KategoriController.cs, tambahkan using Microsoft.AspNetCore.Authorization; lalu tempelkan [Authorize] di atas class (atau method tertentu). Misalnya:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class KategoriController : ControllerBase
{
// ... semua method di dalamnya hanya bisa diakses dengan token valid
}
Kalau mau method GET tetap terbuka untuk umum, tapi POST, PUT, DELETE hanya untuk yang login, kita bisa letakkan [Authorize] hanya pada method-method tersebut.
Contoh di WisataController.cs:
// GET tetap publik
[HttpGet]
public async Task<...> GetWisata() { ... }
[Authorize]
[HttpPost]
public async Task<...> PostWisata(...) { ... }
Sesuaikan dengan kebutuhan. Untuk latihan, kita akan amankan semua method kecuali GET yang bersifat publik.
Langkah 8: Uji Coba dengan Postman
Jalankan API (F5). Buka Postman.
1. Registrasi User Baru
- Method: POST
- URL:
https://localhost:7000/api/auth/register - Body (raw JSON):
{ "username": "joko", "email": "joko@example.com", "password": "rahasia123" } - Klik Send. Response: "Registrasi berhasil."
2. Login
- Method: POST
- URL:
https://localhost:7000/api/auth/login - Body:
{ "username": "joko", "password": "rahasia123" } - Klik Send. Response akan berisi token, misalnya:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } - Salin token tersebut (tanpa tanda kutip).
3. Akses Endpoint yang Dilindungi Tanpa Token
- Coba POST ke
/api/kategoritanpa mengirim token. - Hasilnya: 401 Unauthorized. Bagus, berarti endpoint sudah diamankan.
4. Akses Endpoint dengan Token
- Di Postman, pilih tab Authorization.
- Pilih type Bearer Token.
- Tempel token yang tadi didapat di field Token.
- Kirim request POST ke
/api/kategoridengan data kategori baru (misal "Taman Safari"). - Seharusnya berhasil (201 Created).
Cara lain: tambahkan header manual: Authorization: Bearer <token> di tab Headers.
5. Cek Endpoint Publik (GET)
- GET ke
/api/kategoritanpa token harus tetap bisa. - Ini karena kita tidak mengamankan method GET.
Penjelasan Tambahan
Mengapa pakai BCrypt?
BCrypt adalah algoritma hashing yang dirancang khusus untuk password. Dia lambat (secara sengaja) sehingga sulit ditebak dengan brute force. Jangan pernah pakai hash cepat seperti MD5 atau SHA untuk password.
Apa itu Claims?
Claims adalah potongan data yang disimpan di dalam token. Biasanya berisi informasi pengguna seperti id, username, email. Nanti di controller kita bisa mengambil data ini melalui User.FindFirstValue(ClaimTypes.NameIdentifier).
Bagaimana cara mengambil data user dari token di controller?
Contoh di method POST kategori, kita bisa tahu siapa yang menambah data dengan membaca claim. Tapi untuk sekarang kita tidak pakai dulu.
Membersihkan dan Menambahkan Validasi
Kita mungkin perlu menambahkan role nantinya (admin, user biasa). Tapi untuk sekarang, fitur login dasar sudah cukup.
Jangan lupa, di production, set RequireHttpsMetadata = true dan pastikan kunci JWT disimpan di tempat aman (misal environment variable, Azure Key Vault).
Selamat! API Kamu Sekarang Punya Sistem Keamanan
Sekarang API-mu sudah memiliki sistem autentikasi. Pengguna harus login dulu untuk bisa mengubah data. Ini langkah besar menuju aplikasi yang profesional.
Di artikel berikutnya (ASP.NET Core API #8), kita akan membahas lebih dalam tentang authorization dan mungkin menambahkan role admin. Sampai jumpa!
Ditulis oleh Kakak programmer yang dulu juga suka lupa password. Kalau ada pertanyaan, tulis di komentar ya!