Lewati ke konten utama

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 AkhirMetodeAuthBatas LajuDeskripsi
api/auth/loginPOSTAnonim5 rek/menitAutentikasi user dan terbitkan JWT + refresh token
api/auth/registerPOSTAnonim3 rek/menitBuat akun user baru dan terbitkan JWT + refresh
api/auth/meGETTerotorisasiAmbil profil user saat ini dari klaim JWT
api/auth/logoutPOSTTerotorisasiCabut token dan bersihkan sesi
api/auth/refreshPOSTAnonimTukar refresh token dengan JWT + refresh baru

Alur Login (AuthService.LoginAsync)

  1. Validasi: Validasi email dan kata sandi tidak boleh kosong.
  2. Normalisasi: Normalisasi email menjadi huruf kecil dan hapus spasi tambahan (trim).
  3. Pencarian: Cari user berdasarkan email melalui IUserRepository.GetByEmailAsync (case-insensitive).
  4. Verifikasi: Verifikasi kata sandi menggunakan BCrypt (PasswordService.VerifyPassword).
  5. Pemuatan Data: Muat user dengan detailnya melalui IUserRepository.GetByIdWithDetailsAsync (termasuk Organisasi, UserRoles → Role → Permissions).
  6. Pembuatan Token: Buat token JWT melalui JwtService.GenerateToken (termasuk Klaim: NameIdentifier, Name, Email, Sub, Jti, organization_id, Role, permission[]).
  7. Pembuatan Refresh: Buat refresh token melalui JwtService.GenerateRefreshToken.
  8. Penyimpanan Sesi: Simpan sesi di Redis melalui RedisService.SetUserSessionAsync (TTL 24 jam).
  9. Penyimpanan Refresh: Simpan refresh token di Redis melalui RedisService.SetRefreshTokenAsync (TTL 30 hari).
  10. Respons: Kembalikan token + refresh token + data user (id, nama, email, org, peran dengan izinnya).

Alur Pendaftaran (AuthService.RegisterAsync)

  1. Validasi: Validasi nama, email, kata sandi (min 6 karakter).
  2. Pemeriksaan Duplikat: Periksa duplikasi email melalui IUserRepository.EmailExistsAsync (case-insensitive).
  3. Pemeriksaan Organisasi: Verifikasi organisasi ada melalui IUserRepository.OrganizationExistsAsync jika OrganizationId diberikan.
  4. Hashing: Hash kata sandi dengan BCrypt (PasswordService.HashPassword).
  5. Pembuatan Entitas: Buat entitas user melalui IUserRepository.AddAsync dan simpan.
  6. 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.
  7. Pemuatan Data: Muat user dengan detailnya melalui IUserRepository.GetByIdWithDetailsAsync.
  8. Pembuatan Token: Buat token JWT + refresh token.
  9. Penyimpanan Sesi: Simpan refresh token di Redis (TTL 30 hari).
  10. Respons: Kembalikan token + refresh token + data user.

Alur Refresh Token (AuthService.RefreshTokenAsync)

  1. Validasi: Validasi refresh token tidak boleh kosong.
  2. Pencarian: Cari user dengan mencocokkan refresh token di Redis menggunakan reverse index lookup (refresh_reverse:{token} → userId) untuk penyelesaian O(1).
  3. Pemuatan Data: Muat user dengan detail (organisasi, peran, izin).
  4. Pembuatan Token: Buat token JWT baru + refresh token baru.
  5. Pembaruan Sesi: Rotasi reverse index refresh token dan simpan sesi baru + refresh token di Redis.
  6. Respons: Kembalikan token baru + refresh token baru + data user.

Alur Logout (AuthService.LogoutAsync)

  1. Ekstraksi: Ekstrak token bearer dari header Authorization.
  2. Identifikasi User: Ekstrak ID user dari klaim JWT melalui UserHelper.GetUserId.
  3. Pembersihan Sesi: Hapus sesi user dari Redis (RedisService.DeleteUserSessionAsync).
  4. Blacklisting: Masukkan token ke daftar hitam di Redis (RedisService.BlacklistTokenAsync, TTL 24 jam).
  5. Pembersihan Refresh: Hapus refresh token + reverse index untuk user tersebut.
  6. 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:

KlaimSumberDeskripsi
ClaimTypes.NameIdentifieruser.IdGUID Pengguna
ClaimTypes.Nameuser.NameNama tampilan
ClaimTypes.Emailuser.EmailAlamat email
JwtRegisteredClaimNames.Subuser.IdKlaim subject standar
JwtRegisteredClaimNames.Emailuser.EmailKlaim email standar
JwtRegisteredClaimNames.JtiGuid.NewGuid()ID unik token
organization_idGUID OrganisasiOrganisasi pengguna
ClaimTypes.RoleNama PeranNama peran pengguna
permissionSlug IzinKlaim otorisasi kebijakan

Konfigurasi Token

PengaturanSumberDefault
SecretJwtSettings:Secret(wajib, ≥ 32 kar)
IssuerJwtSettings:IssuerRavenxcope.Backend
AudienceJwtSettings:AudienceRavenxcopeUsers
ExpiryJwtSettings:ExpiryMinutes1440 (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 AkhirMetodeDeskripsi
api/usersGETDaftar user di organisasi saat ini
api/users/paginatedGETDaftar user dengan paginasi
api/users/{id}GETAmbil user berdasarkan ID + izin
api/usersPOSTBuat user dengan peran
api/users/{id}PUTPerbarui detail user dan peran
api/users/{id}DELETEHapus 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

  1. Validasi: Validasi nama, email, kata sandi (min 6 karakter).
  2. Pemeriksaan Duplikat: Periksa duplikasi email (case-insensitive).
  3. Verifikasi: Verifikasi organisasi dan peran ada jika disediakan.
  4. Hashing: Hash kata sandi dengan BCrypt.
  5. Pembuatan Entitas: Buat entitas user.
  6. Penetapan Peran: Jika roleId diberikan, buat rekaman junction UserRole.
  7. Pemuatan Ulang Data: Muat ulang user dengan properti navigasi.
  8. Respons: Kembalikan 201 Created dengan data user.

Pembaruan Pengguna

  1. Pencarian: Cari user berdasarkan ID (404 jika tidak ditemukan).
  2. Perbarui Nama: Perbarui nama jika disediakan.
  3. Perbarui Email: Perbarui email jika disediakan (periksa duplikat).
  4. Perbarui Kata Sandi: Perbarui kata sandi jika disediakan (min 6 kar, hash dengan BCrypt).
  5. Perbarui Organisasi: Perbarui organisasi jika disediakan.
  6. Perbarui Peran: Jika roleId disediakan:
    • Hapus semua rekaman UserRole yang ada.
    • Buat rekaman UserRole baru.
  7. 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 BCrypt
  • VerifyPassword(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

PerilakuImplementasi
Penegakan autentikasiAtribut [Authorize] pada controller
Akses titik akhir publikAtribut [AllowAnonymous] pada aksi
Daftar hitam tokenBerbasis Redis via JwtBearerEvents.OnTokenValidated
Manajemen sesiRedis dengan TTL 24 jam
Penyimpanan refresh tokenRedis dengan TTL 30 hari
Penyimpanan kata sandiHash BCrypt (tidak pernah disimpan teks asli)
Kebijakan otorisasimanage-users, manage-sensors, manage-assets, manage-roles, edit-organization
Peringatan secret JWTPeringatan startup jika secret < 32 karakter
Batas laju (login)Jendela tetap: 5 permintaan per menit
Batas laju (daftar)Jendela tetap: 3 permintaan per menit
Amplop responsSemua titik akhir menggunakan ApiEnvelope.Success/Error
Penanganan pengecualianPenangan 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.