Autentikasi dan Pengguna
Pendahuluan
Dokumen ini mencakup fitur autentikasi dan manajemen pengguna dari backend Ravenxcope. Autentikasi ditangani melalui token JWT dengan dukungan refresh token, manajemen sesi berbasis Redis, dan pembuatan daftar hitam (blacklisting) token. Manajemen pengguna menyediakan operasi CRUD dengan cakupan organisasi beserta penetapan peran. Semua logika bisnis dienkapsulasi dalam AuthService dan UserService, dengan controller bertindak sebagai lapisan transport HTTP tipis.
Modul Autentikasi
Controller
File: API/Controllers/Auth/AuthController.cs
Rute: api/auth
Mewarisi: ControllerBase
Delegasi ke: IAuthService (semua logika bisnis)
Titik Akhir (Endpoints)
| Titik Akhir | Metode | Auth | Batas Laju | Deskripsi |
|---|---|---|---|---|
api/auth/login | POST | Anonim | 5 rek/menit | Autentikasi user dan terbitkan JWT + refresh token |
api/auth/register | POST | Anonim | 3 rek/menit | Buat akun user baru dan terbitkan JWT + refresh |
api/auth/me | GET | Terotorisasi | — | Ambil profil user saat ini dari klaim JWT |
api/auth/logout | POST | Terotorisasi | — | Cabut token dan bersihkan sesi |
api/auth/refresh | POST | Anonim | — | Tukar refresh token dengan JWT + refresh baru |
Alur Login (AuthService.LoginAsync)
- Validasi: Validasi email dan kata sandi tidak boleh kosong.
- Normalisasi: Normalisasi email menjadi huruf kecil dan hapus spasi tambahan (trim).
- Pencarian: Cari user berdasarkan email melalui
IUserRepository.GetByEmailAsync(case-insensitive). - Verifikasi: Verifikasi kata sandi menggunakan BCrypt (
PasswordService.VerifyPassword). - Pemuatan Data: Muat user dengan detailnya melalui
IUserRepository.GetByIdWithDetailsAsync(termasuk Organisasi, UserRoles → Role → Permissions). - Pembuatan Token: Buat token JWT melalui
JwtService.GenerateToken(termasuk Klaim: NameIdentifier, Name, Email, Sub, Jti, organization_id, Role, permission[]). - Pembuatan Refresh: Buat refresh token melalui
JwtService.GenerateRefreshToken. - Penyimpanan Sesi: Simpan sesi di Redis melalui
RedisService.SetUserSessionAsync(TTL 24 jam). - Penyimpanan Refresh: Simpan refresh token di Redis melalui
RedisService.SetRefreshTokenAsync(TTL 30 hari). - Respons: Kembalikan token + refresh token + data user (id, nama, email, org, peran dengan izinnya).
Alur Pendaftaran (AuthService.RegisterAsync)
- Validasi: Validasi nama, email, kata sandi (min 6 karakter).
- Pemeriksaan Duplikat: Periksa duplikasi email melalui
IUserRepository.EmailExistsAsync(case-insensitive). - Pemeriksaan Organisasi: Verifikasi organisasi ada melalui
IUserRepository.OrganizationExistsAsyncjika OrganizationId diberikan. - Hashing: Hash kata sandi dengan BCrypt (
PasswordService.HashPassword). - Pembuatan Entitas: Buat entitas user melalui
IUserRepository.AddAsyncdan simpan. - Penetapan Peran: Jika RoleId diberikan:
- Cari peran berdasarkan ID melalui
IRoleRepository.GetByIdWithPermissionsAsync. - Jika peran adalah "Super Admin" atau "Admin" dan tidak memiliki izin, tetapkan secara otomatis SEMUA izin dari katalog.
- Buat rekaman junction UserRole melalui
IUserRoleRepository.AddAsync.
- Cari peran berdasarkan ID melalui
- Pemuatan Data: Muat user dengan detailnya melalui
IUserRepository.GetByIdWithDetailsAsync. - Pembuatan Token: Buat token JWT + refresh token.
- Penyimpanan Sesi: Simpan refresh token di Redis (TTL 30 hari).
- Respons: Kembalikan token + refresh token + data user.
Alur Refresh Token (AuthService.RefreshTokenAsync)
- Validasi: Validasi refresh token tidak boleh kosong.
- Pencarian: Cari user dengan mencocokkan refresh token di Redis menggunakan reverse index lookup (
refresh_reverse:{token}→ userId) untuk penyelesaian O(1). - Pemuatan Data: Muat user dengan detail (organisasi, peran, izin).
- Pembuatan Token: Buat token JWT baru + refresh token baru.
- Pembaruan Sesi: Rotasi reverse index refresh token dan simpan sesi baru + refresh token di Redis.
- Respons: Kembalikan token baru + refresh token baru + data user.
Alur Logout (AuthService.LogoutAsync)
- Ekstraksi: Ekstrak token bearer dari header Authorization.
- Identifikasi User: Ekstrak ID user dari klaim JWT melalui
UserHelper.GetUserId. - Pembersihan Sesi: Hapus sesi user dari Redis (
RedisService.DeleteUserSessionAsync). - Blacklisting: Masukkan token ke daftar hitam di Redis (
RedisService.BlacklistTokenAsync, TTL 24 jam). - Pembersihan Refresh: Hapus refresh token + reverse index untuk user tersebut.
- Respons: Kembalikan pesan sukses.
Penegakan Daftar Hitam (Blacklist) Token
Daftar hitam token ditegakkan pada level middleware autentikasi melalui JwtBearerEvents.OnTokenValidated:
options.Events = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
var redisService = context.HttpContext.RequestServices
.GetRequiredService<IRedisService>();
var token = BearerTokenHelper.GetBearerToken(context.Request);
if (string.IsNullOrWhiteSpace(token))
{
context.Fail("Token hilang atau tidak valid");
return;
}
if (await redisService.IsTokenBlacklistedAsync(token))
{
context.Fail("Token telah dicabut");
}
}
};
Ini berjalan pada setiap permintaan yang terautentikasi, memastikan token yang telah dicabut segera ditolak.
Layanan JWT
File: Infrastructure/Services/JwtService.cs
Masa Pakai (Lifetime): Singleton
Pembuatan Token
JwtService menghasilkan token JWT dengan klaim berikut:
| Klaim | Sumber | Deskripsi |
|---|---|---|
ClaimTypes.NameIdentifier | user.Id | GUID Pengguna |
ClaimTypes.Name | user.Name | Nama tampilan |
ClaimTypes.Email | user.Email | Alamat email |
JwtRegisteredClaimNames.Sub | user.Id | Klaim subject standar |
JwtRegisteredClaimNames.Email | user.Email | Klaim email standar |
JwtRegisteredClaimNames.Jti | Guid.NewGuid() | ID unik token |
organization_id | GUID Organisasi | Organisasi pengguna |
ClaimTypes.Role | Nama Peran | Nama peran pengguna |
permission | Slug Izin | Klaim otorisasi kebijakan |
Konfigurasi Token
| Pengaturan | Sumber | Default |
|---|---|---|
| Secret | JwtSettings:Secret | (wajib, ≥ 32 kar) |
| Issuer | JwtSettings:Issuer | Ravenxcope.Backend |
| Audience | JwtSettings:Audience | RavenxcopeUsers |
| Expiry | JwtSettings:ExpiryMinutes | 1440 (24 jam) |
| Algoritma | (hardcoded) | HmacSha256 |
| Clock Skew | (hardcoded) | TimeSpan.Zero |
Validasi
Metode ValidateToken dan middleware JWT Bearer menegakkan:
- Validasi Issuer (harus cocok dengan issuer yang dikonfigurasi)
- Validasi Audience (harus cocok dengan audience yang dikonfigurasi)
- Validasi Masa Pakai (token tidak boleh kadaluwarsa)
- Validasi Kunci Penandatanganan (HMAC-SHA256 dengan kunci simetris)
- Clock skew nol (pencocokan waktu yang ketat)
Modul Manajemen Pengguna
Controller
File: API/Controllers/Users/UsersController.cs
Rute: api/users
Mewarisi: ControllerBase
Auth: [Authorize] (semua titik akhir memerlukan autentikasi)
Delegasi ke: IUserService (semua logika bisnis)
Titik Akhir (Endpoints)
| Titik Akhir | Metode | Deskripsi |
|---|---|---|
api/users | GET | Daftar user di organisasi saat ini |
api/users/paginated | GET | Daftar user dengan paginasi |
api/users/{id} | GET | Ambil user berdasarkan ID + izin |
api/users | POST | Buat user dengan peran |
api/users/{id} | PUT | Perbarui detail user dan peran |
api/users/{id} | DELETE | Hapus permanen user |
Pembatasan Organisasi
Titik akhir daftar user mengambil ID organisasi dari klaim JWT:
var organizationId = UserHelper.GetOrganizationId(User);
if (!organizationId.HasValue)
{
return BadRequest(ApiEnvelope.Error("ID Organisasi tidak ditemukan dalam token", HttpContext.TraceIdentifier));
}
var users = await _userService.GetUsersAsync(organizationId.Value);
Paginasi
Titik akhir GET api/users/paginated mendukung paginasi melalui parameter kueri:
GET /api/users/paginated?page=1&pageSize=10
Respons mencakup metadata paginasi:
{
"success": true,
"data": {
"items": [...],
"pagination": {
"currentPage": 1,
"pageSize": 10,
"totalCount": 42,
"totalPages": 5
}
},
"errors": []
}
Pembuatan Pengguna
- Validasi: Validasi nama, email, kata sandi (min 6 karakter).
- Pemeriksaan Duplikat: Periksa duplikasi email (case-insensitive).
- Verifikasi: Verifikasi organisasi dan peran ada jika disediakan.
- Hashing: Hash kata sandi dengan BCrypt.
- Pembuatan Entitas: Buat entitas user.
- Penetapan Peran: Jika roleId diberikan, buat rekaman junction UserRole.
- Pemuatan Ulang Data: Muat ulang user dengan properti navigasi.
- Respons: Kembalikan 201 Created dengan data user.
Pembaruan Pengguna
- Pencarian: Cari user berdasarkan ID (404 jika tidak ditemukan).
- Perbarui Nama: Perbarui nama jika disediakan.
- Perbarui Email: Perbarui email jika disediakan (periksa duplikat).
- Perbarui Kata Sandi: Perbarui kata sandi jika disediakan (min 6 kar, hash dengan BCrypt).
- Perbarui Organisasi: Perbarui organisasi jika disediakan.
- Perbarui Peran: Jika roleId disediakan:
- Hapus semua rekaman UserRole yang ada.
- Buat rekaman UserRole baru.
- Respons: Kembalikan data user yang diperbarui.
Penghapusan Pengguna
User dihapus secara permanen (hard delete, bukan soft delete). Aturan cascade delete akan menangani penghapusan rekaman UserRole terkait secara otomatis.
Hashing Kata Sandi
File: Infrastructure/Services/PasswordService.cs
Pustaka: BCrypt.Net-Next
Layanan menyediakan metode statis:
HashPassword(string password)— Hash BCryptVerifyPassword(string password, string hash)— Verifikasi BCrypt
Kelas Pembantu (Helper)
BearerTokenHelper
File: Common/Helpers/BearerTokenHelper.cs
Mengekstrak token JWT dari header Authorization: Bearer {token}:
public static string? GetBearerToken(HttpRequest request)
UserHelper
File: Common/Helpers/UserHelper.cs
Mengekstrak klaim dari prinsipal pengguna yang terautentikasi:
public static Guid? GetOrganizationId(ClaimsPrincipal user)
public static Guid? GetUserId(ClaimsPrincipal user)
Perilaku Keamanan
| Perilaku | Implementasi |
|---|---|
| Penegakan autentikasi | Atribut [Authorize] pada controller |
| Akses titik akhir publik | Atribut [AllowAnonymous] pada aksi |
| Daftar hitam token | Berbasis Redis via JwtBearerEvents.OnTokenValidated |
| Manajemen sesi | Redis dengan TTL 24 jam |
| Penyimpanan refresh token | Redis dengan TTL 30 hari |
| Penyimpanan kata sandi | Hash BCrypt (tidak pernah disimpan teks asli) |
| Kebijakan otorisasi | manage-users, manage-sensors, manage-assets, manage-roles, edit-organization |
| Peringatan secret JWT | Peringatan startup jika secret < 32 karakter |
| Batas laju (login) | Jendela tetap: 5 permintaan per menit |
| Batas laju (daftar) | Jendela tetap: 3 permintaan per menit |
| Amplop respons | Semua titik akhir menggunakan ApiEnvelope.Success/Error |
| Penanganan pengecualian | Penangan global — tanpa try-catch di controller |
Perbaikan di Masa Depan
Selesai
- Amplop respons standar (
ApiEnvelope). - Mekanisme refresh token (
POST api/auth/refresh). - Logika bisnis dipindahkan ke lapisan layanan (
AuthService,UserService). - Pembatasan laju untuk titik akhir login/daftar (kebijakan fixed-window).
Terbuka
- Menambahkan tes integrasi khusus untuk kasus batas pencabutan dan penyegaran token.