Bagikan melalui


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

Membuat API web ASP.NET Core

  1. Buka terminal Anda, lalu navigasikan ke folder tempat Anda ingin proyek Anda tinggal.

  2. Jalankan perintah berikut:

    dotnet new webapi -o ToDoListAPI
    cd ToDoListAPI
    
  3. 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.Webmenyederhanakan 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:

  1. Gunakan langkah-langkah dalam Mengaktifkan domain URL kustom untuk aplikasi di penyewa eksternal untuk mengaktifkan domain URL kustom untuk penyewa eksternal Anda.

  2. Buka file appsettings.json :

    1. Perbarui nilai properti ke Instance https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here. Ganti Enter_the_Custom_Domain_Here dengan domain URL kustom Anda dan Enter_the_Tenant_ID_Here dengan ID penyewa Anda. Jika Anda tidak memiliki ID penyewa, pelajari cara membaca detail penyewa Anda.
    2. Tambahkan knownAuthorities properti dengan nilai [Enter_the_Custom_Domain_Here].

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.

  1. Buat folder bernama DbContext di folder akar proyek.

  2. 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; }
    }
    
  3. Buka file Program.cs di folder akar aplikasi Anda, lalu tambahkan kode berikut dalam file. Kode ini mendaftarkan subkelas yang DbContext disebut ToDoContext 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.

  1. Navigasikan ke folder Pengontrol di folder akar proyek Anda.

  2. 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.

  1. 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.

  2. 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 ini idtyp adalah cara term mudah bagi API untuk menentukan apakah token adalah token aplikasi atau token aplikasi + pengguna. Sebaiknya aktifkan idtyp klaim opsional.

    idtyp Jika klaim tidak diaktifkan, Anda dapat menggunakan roles klaim dan scp 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 memiliki scp klaim. Token akses yang dikeluarkan ke aplikasi memiliki roles klaim. Token akses yang berisi kedua klaim hanya dikeluarkan untuk pengguna, di mana scp klaim menunjuk izin yang didelegasikan, sementara roles 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");
        }
    }
    
  3. 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;
        }
    
  4. 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.

Langkah selanjutnya