Tutorial: Mengamankan API web ASP.NET Core yang terdaftar di penyewa eksternal
Seri tutorial ini menunjukkan cara mengamankan API web terdaftar di penyewa eksternal. Dalam tutorial ini, Anda akan membangun API web ASP.NET Core yang menerbitkan izin yang didelegasikan (cakupan) dan izin aplikasi (peran aplikasi).
Dalam tutorial ini;
- Mengonfigurasi API web Anda untuk menggunakan detail pendaftaran aplikasinya
- Mengonfigurasi API web Anda untuk menggunakan izin yang didelegasikan dan izin aplikasi yang terdaftar dalam pendaftaran aplikasi
- Melindungi titik akhir API web Anda
Prasyarat
Pendaftaran API yang mengekspos setidaknya satu cakupan (izin yang didelegasikan) dan satu peran aplikasi (izin aplikasi) seperti ToDoList.Read. Jika Anda belum melakukannya, daftarkan API di pusat admin Microsoft Entra dengan mengikuti langkah-langkah pendaftaran. Pastikan Anda memiliki hal berikut:
- ID aplikasi (klien) API Web
- ID Direktori (penyewa) API Web terdaftar
- Subdomain direktori (penyewa) tempat API Web didaftarkan. Misalnya, jika domain utama Anda contoso.onmicrosoft.com, subdomain Direktori (penyewa) Anda adalah contoso.
- ToDoList.Read dan ToDoList.ReadWrite sebagai izin (cakupan) yang didelegasikan yang diekspos oleh API Web.
- ToDoList.Read.All dan ToDoList.ReadWrite.All sebagai izin aplikasi (peran aplikasi) yang diekspos oleh API Web.
.NET 7.0 SDK atau yang lebih baru.
Visual Studio Code, atau editor kode lainnya.
Membuat API web ASP.NET Core
Buka terminal Anda, lalu navigasikan ke folder tempat Anda ingin proyek Anda tinggal.
Jalankan perintah berikut:
dotnet new webapi -o ToDoListAPI cd ToDoListAPI
Saat kotak dialog menanyakan apakah Anda ingin menambahkan aset yang diperlukan ke proyek, pilih Ya.
Memasang paket
Instal paket berikut:
Microsoft.EntityFrameworkCore.InMemory
yang memungkinkan Entity Framework Core digunakan dengan database dalam memori. Ini tidak dirancang untuk penggunaan produksi.Microsoft.Identity.Web
menyederhanakan penambahan dukungan autentikasi dan otorisasi ke aplikasi web dan API web yang terintegrasi dengan platform identitas Microsoft.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web
Mengonfigurasi detail pendaftaran aplikasi
Buka file appsettings.json di folder aplikasi Anda dan tambahkan detail pendaftaran aplikasi yang Anda rekam setelah mendaftarkan API web Anda.
{
"AzureAd": {
"Instance": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/",
"TenantId": "Enter_the_Tenant_Id_Here",
"ClientId": "Enter_the_Application_Id_Here",
},
"Logging": {...},
"AllowedHosts": "*"
}
Ganti tempat penampung berikut seperti yang diperlihatkan:
- Ganti
Enter_the_Application_Id_Here
dengan ID aplikasi (klien) Anda. - Ganti
Enter_the_Tenant_Id_Here
dengan ID Direktori (penyewa) Anda. - Ganti
Enter_the_Tenant_Subdomain_Here
dengan subdomain Direktori (penyewa) Anda.
Menggunakan domain URL kustom (Opsional)
Gunakan domain kustom untuk sepenuhnya memberi merek URL autentikasi. Dari perspektif pengguna, pengguna tetap berada di domain Anda selama proses autentikasi, daripada dialihkan ke nama domain ciamlogin.com .
Ikuti langkah-langkah ini untuk menggunakan domain kustom:
Gunakan langkah-langkah dalam Mengaktifkan domain URL kustom untuk aplikasi di penyewa eksternal untuk mengaktifkan domain URL kustom untuk penyewa eksternal Anda.
Buka file appsettings.json :
- Perbarui nilai properti ke
Instance
https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here. GantiEnter_the_Custom_Domain_Here
dengan domain URL kustom Anda danEnter_the_Tenant_ID_Here
dengan ID penyewa Anda. Jika Anda tidak memiliki ID penyewa, pelajari cara membaca detail penyewa Anda. - Tambahkan
knownAuthorities
properti dengan nilai [Enter_the_Custom_Domain_Here].
- Perbarui nilai properti ke
Setelah Anda membuat perubahan pada file appsettings.json Anda, jika domain URL kustom Anda login.contoso.com, dan ID penyewa Anda adalah aaaabbbb-0000-cc-1111-dddd2222eeee, maka file Anda akan terlihat mirip dengan cuplikan berikut:
{
"AzureAd": {
"Instance": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
"TenantId": "Enter_the_Tenant_Id_Here",
"ClientId": "Enter_the_Application_Id_Here",
"KnownAuthorities": ["login.contoso.com"]
},
"Logging": {...},
"AllowedHosts": "*"
}
Menambahkan peran dan cakupan aplikasi
Semua API harus menerbitkan minimal satu cakupan, juga disebut izin yang didelegasikan, agar aplikasi klien mendapatkan token akses bagi pengguna dengan sukses. API juga harus menerbitkan minimal satu peran aplikasi untuk aplikasi, juga disebut izin aplikasi, agar aplikasi klien mendapatkan token akses sebagai diri mereka sendiri, yaitu, ketika mereka tidak masuk ke pengguna.
Kami menentukan izin ini dalam file appsettings.json . Dalam tutorial ini, kami telah mendaftarkan empat izin. ToDoList.ReadWrite dan ToDoList.Read sebagai izin yang didelegasikan, dan ToDoList.ReadWrite.All dan ToDoList.Read.All sebagai izin aplikasi.
{
"AzureAd": {
"Instance": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/",
"TenantId": "Enter_the_Tenant_Id_Here",
"ClientId": "Enter_the_Application_Id_Here",
"Scopes": {
"Read": ["ToDoList.Read", "ToDoList.ReadWrite"],
"Write": ["ToDoList.ReadWrite"]
},
"AppPermissions": {
"Read": ["ToDoList.Read.All", "ToDoList.ReadWrite.All"],
"Write": ["ToDoList.ReadWrite.All"]
}
},
"Logging": {...},
"AllowedHosts": "*"
}
Menambahkan skema autentikasi
Skema autentikasi diberi nama saat layanan autentikasi dikonfigurasi selama autentikasi. Dalam artikel ini, kami menggunakan skema autentikasi pembawa JWT. Tambahkan kode berikut dalam file Programs.cs untuk menambahkan skema autentikasi.
// Add the following to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
// Add authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration);
Buat model Anda
Buat folder bernama Model di folder akar proyek Anda. Navigasi ke folder dan buat file bernama ToDo.cs lalu tambahkan kode berikut. Kode ini membuat model yang disebut ToDo.
using System;
namespace ToDoListAPI.Models;
public class ToDo
{
public int Id { get; set; }
public Guid Owner { get; set; }
public string Description { get; set; } = string.Empty;
}
Tambahkan konteks database
Konteks database adalah kelas utama yang mengoordinasikan fungsionalitas Kerangka Kerja Entitas untuk model data. Kelas ini dibuat dengan berasal dari kelas Microsoft.EntityFrameworkCore.DbContext . Dalam tutorial ini, kita menggunakan database dalam memori untuk tujuan pengujian.
Buat folder bernama DbContext di folder akar proyek.
Navigasikan ke folder tersebut dan buat file yang disebut ToDoContext.cs lalu tambahkan konten berikut ke file tersebut:
using Microsoft.EntityFrameworkCore; using ToDoListAPI.Models; namespace ToDoListAPI.Context; public class ToDoContext : DbContext { public ToDoContext(DbContextOptions<ToDoContext> options) : base(options) { } public DbSet<ToDo> ToDos { get; set; } }
Buka file Program.cs di folder akar aplikasi Anda, lalu tambahkan kode berikut dalam file. Kode ini mendaftarkan subkelas yang
DbContext
disebutToDoContext
sebagai layanan terlingkup di penyedia layanan aplikasi ASP.NET Core (juga dikenal sebagai, kontainer injeksi dependensi). Konteks dikonfigurasi untuk menggunakan database dalam memori.// Add the following to your imports using ToDoListAPI.Context; using Microsoft.EntityFrameworkCore; builder.Services.AddDbContext<ToDoContext>(opt => opt.UseInMemoryDatabase("ToDos"));
Menambahkan pengontrol
Dalam kebanyakan kasus, pengontrol akan memiliki lebih dari satu tindakan. Biasanya tindakan Buat, Baca, Perbarui, dan Hapus (CRUD). Dalam tutorial ini, kami hanya membuat dua item tindakan. Baca semua item tindakan dan buat item tindakan untuk menunjukkan cara melindungi titik akhir Anda.
Navigasikan ke folder Pengontrol di folder akar proyek Anda.
Buat file bernama ToDoListController.cs di dalam folder ini. Buka file lalu tambahkan kode pelat boiler berikut:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using Microsoft.Identity.Web.Resource; using ToDoListAPI.Models; using ToDoListAPI.Context; namespace ToDoListAPI.Controllers; [Authorize] [Route("api/[controller]")] [ApiController] public class ToDoListController : ControllerBase { private readonly ToDoContext _toDoContext; public ToDoListController(ToDoContext toDoContext) { _toDoContext = toDoContext; } [HttpGet()] [RequiredScopeOrAppPermission()] public async Task<IActionResult> GetAsync(){...} [HttpPost] [RequiredScopeOrAppPermission()] public async Task<IActionResult> PostAsync([FromBody] ToDo toDo){...} private bool RequestCanAccessToDo(Guid userId){...} private Guid GetUserId(){...} private bool IsAppMakingRequest(){...} }
Menambahkan kode ke pengontrol
Di bagian ini, kami menambahkan kode ke tempat penampung yang kami buat. Fokus di sini bukan untuk membangun API, melainkan melindunginya.
Impor paket yang diperlukan. Paket Microsoft.Identity.Web adalah pembungkus MSAL yang membantu kami menangani logika autentikasi dengan mudah, misalnya, dengan menangani validasi token. Untuk memastikan bahwa titik akhir kami memerlukan otorisasi, kami menggunakan paket Microsoft.AspNetCore.Authorization bawaan.
Karena kami memberikan izin agar API ini dipanggil baik menggunakan izin yang didelegasikan atas nama pengguna atau izin aplikasi di mana klien memanggil sebagai dirinya sendiri dan bukan atas nama pengguna, penting untuk mengetahui apakah panggilan dilakukan oleh aplikasi atas nama sendiri. Cara term mudah untuk melakukan ini adalah klaim untuk menemukan apakah token akses berisi
idtyp
klaim opsional. Klaim iniidtyp
adalah cara term mudah bagi API untuk menentukan apakah token adalah token aplikasi atau token aplikasi + pengguna. Sebaiknya aktifkanidtyp
klaim opsional.idtyp
Jika klaim tidak diaktifkan, Anda dapat menggunakanroles
klaim danscp
untuk menentukan apakah token akses adalah token aplikasi atau token aplikasi + pengguna. Token akses yang dikeluarkan oleh MICROSOFT Entra External ID memiliki setidaknya salah satu dari dua klaim. Token akses yang dikeluarkan untuk pengguna memilikiscp
klaim. Token akses yang dikeluarkan ke aplikasi memilikiroles
klaim. Token akses yang berisi kedua klaim hanya dikeluarkan untuk pengguna, di manascp
klaim menunjuk izin yang didelegasikan, sementararoles
klaim menunjuk peran pengguna. Token akses yang tidak memiliki tidak boleh dihormati.private bool IsAppMakingRequest() { if (HttpContext.User.Claims.Any(c => c.Type == "idtyp")) { return HttpContext.User.Claims.Any(c => c.Type == "idtyp" && c.Value == "app"); } else { return HttpContext.User.Claims.Any(c => c.Type == "roles") && !HttpContext.User.Claims.Any(c => c.Type == "scp"); } }
Tambahkan fungsi pembantu yang menentukan apakah permintaan yang dibuat berisi izin yang cukup untuk melakukan tindakan yang dimaksudkan. Periksa apakah aplikasi yang membuat permintaan atas nama sendiri atau apakah aplikasi melakukan panggilan atas nama pengguna yang memiliki sumber daya yang diberikan dengan memvalidasi ID pengguna.
private bool RequestCanAccessToDo(Guid userId) { return IsAppMakingRequest() || (userId == GetUserId()); } private Guid GetUserId() { Guid userId; if (!Guid.TryParse(HttpContext.User.GetObjectId(), out userId)) { throw new Exception("User ID is not valid."); } return userId; }
Colokkan definisi izin Anda untuk melindungi rute. Lindungi API Anda dengan menambahkan
[Authorize]
atribut ke kelas pengontrol. Ini memastikan tindakan pengontrol hanya dapat dipanggil jika API dipanggil dengan identitas yang diotorisasi. Definisi izin menentukan jenis izin apa yang diperlukan untuk melakukan tindakan ini.[Authorize] [Route("api/[controller]")] [ApiController] public class ToDoListController: ControllerBase{...}
Tambahkan izin ke GET semua titik akhir dan titik akhir POST. Lakukan ini menggunakan metode RequiredScopeOrAppPermission yang merupakan bagian dari namespace Microsoft.Identity.Web.Resource . Anda kemudian meneruskan cakupan dan izin ke metode ini melalui atribut RequiredScopesConfigurationKey dan RequiredAppPermissionsConfigurationKey .
[HttpGet] [RequiredScopeOrAppPermission( RequiredScopesConfigurationKey = "AzureAD:Scopes:Read", RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Read" )] public async Task<IActionResult> GetAsync() { var toDos = await _toDoContext.ToDos! .Where(td => RequestCanAccessToDo(td.Owner)) .ToListAsync(); return Ok(toDos); } [HttpPost] [RequiredScopeOrAppPermission( RequiredScopesConfigurationKey = "AzureAD:Scopes:Write", RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Write" )] public async Task<IActionResult> PostAsync([FromBody] ToDo toDo) { // Only let applications with global to-do access set the user ID or to-do's var ownerIdOfTodo = IsAppMakingRequest() ? toDo.Owner : GetUserId(); var newToDo = new ToDo() { Owner = ownerIdOfTodo, Description = toDo.Description }; await _toDoContext.ToDos!.AddAsync(newToDo); await _toDoContext.SaveChangesAsync(); return Created($"/todo/{newToDo!.Id}", newToDo); }
Jalankan API Anda
Jalankan API Anda untuk memastikan bahwa API berjalan dengan baik tanpa kesalahan menggunakan perintah dotnet run
. Jika Anda berniat menggunakan protokol HTTPS bahkan selama pengujian, Anda perlu mempercayai . Sertifikat pengembangan NET.
Untuk contoh lengkap kode API ini, lihat file sampel.