Yazılım

Identity & Access Management İşlemlerini Azure AD B2C ile .NET Ortamında Gerçekleştirmek

Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı sıra, bizlerin farklı yönetimsel masraflardan da olabildiğince kaçınabilmesini sağlamaktadır.

Günümüzde bir çoğumuz cloud-native uygulamalar geliştiriyor veya cloud’a migration’lar gerçekleştiriyor. Bu süreçlerde ise çeşitli masrafları minimize edebilmek için cloud sağlayıcıların farklı PaaSSaaSServerless gibi çözümlerinden de olabildiğince yararlanmaya çalışıyoruz. Azure Active Directory B2C ise consumer identity ve access management konusunda yararlanabileceğimiz cloud çözümlerinden bir tanesi. Özellikle customer-facing bir uygulama geliştiriyorsak, güvenli ve ölçeklenebilir bir consumer identity management özelliğine sahip olmak oldukça önem arz etmektedir.

.NET dünyasında bu tarz ihtiyaçlar için genellikle Identity Server configure edilerek kullanılmaktadır. Elbette diğer servis’lerde de olduğu gibi identity servis’inin de güvenliğini, ölçeklenebilirliğini ve hosting’ini yönetiyor olmamız gerekmektedir. Öte yandan alternatif olarak Azure AD B2C kullanarak bu servis’in güvenliğiniölçeklenebilirliğiniGDPR gibi veri koruma yönetmeliklerine uyumluluğunu ve benzeri karmaşıklıkları bizim yerimize Azure‘un üstlenmesini sağlayabilir ve doğrudan kendi core business’ımıza odaklana da biliriz.

Azure AD B2C

Azure‘un sunmuş olduğu bu servis, consumer identity ve access management konusunda bizlere bir çok fonksiyonalite ve esneklikler sunmaktadır.

  • OAuth 2.0 ve OpenID Connect desteği bulunmaktadır.
  • Customize edilebilir arayüz ile birlikte “sign in”, “sign up” ve “reset password” gibi farklı fonksiyonaliteler de sağlamaktadır.
  • Built-in threat detection ve multi-factor authentication sağlamaktadır.
  • Farklı identity provider’ları ile entegrasyonu bulunmaktadır.
  • Customize edilebilir attribute yapısına izin vermektedir. Yani ihtiyaçlarımız doğrultusunda dilediğimiz user-specific attribute’lere sahip olabiliriz.

Şimdi daha konuyu iyi anlayabilmek adına, Azure AD B2C ile basit bir örnek gerçekleştirelim.

Bir Azure AD B2C Tenant’ı Oluşturalım

Azure AD B2C kullanabilmemiz için öncelikle bir tenant oluşturmamız gerekmektedir. Tenant’ı bir organization’a ait olan tüm kullanıcı bilgilerini, uygulama ve API kayıtlarını ve policy gibi çeşitli resource’larını barındıran isolated bir container olarak düşünebiliriz.

Öncelikle bir tenant oluşturabilmek için buradaki adımları takip edelim. Ben “MyTodo” adında bir organization oluşturdum. Tenant’ı oluşturduktan sonra ise kullanmaya başlayabilmek için ilgili tenant’ı içeren directory’e geçiş yapmamız gerekmektedir. Bunun için Azure portal üzerinden “Directories + subscriptions” bölümüne gidelim ve oluşturmuş olduğumuz directory’e geçiş işlemini gerçekleştirelim.

Geçiş işlemini gerçekleştirdikten sonra artık identity access işlemleri için Azure AD B2C‘yi configure etmeye başlayabiliriz.

Azure AD B2C içerisinde consumer identity ve access management işlemleri için kullanıcıların takip etmeleri gereken çeşitli business logic’ler tanımlayabilmekteyiz. Bu işlemleri ise iki farklı şekilde gerçekleştirebilmekteyiz. Eğer kompleks bir sürecimiz yoksa, örneğin REST çağrıları yapmayı gerektiren işlemler gibi, hali hazırda sunulmuş olan user flow’ları hızlıca kullanmaya başlayabiliriz. Eğer kompleks bir sürece sahipsek ve policy-driven bir yaklaşıma ihtiyacımız varsa, ozaman kendi custom XML-based policy’lerimizi tanımlamamız gerekmektedir.

https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview

Bu makale kapsamında ise predefined sunulan user flow’ları nasıl kullanabileceğimize bir bakacağız. Predefined sunulan user flow’lar ile consumer identity ve access management adına ihtiyacımız olacak olan bir çok işemleri kolaylıkla ve hızlı bir şekilde gerçekleştirebilmekteyiz.

Bu işlemlerin bazıları;

  • Sign-in işlemleri için kullanılıcak account type’ını belirleyebilmekteyiz (social accounts veya local accounts gibi)
  • Kullanıcıdan alacağımız attribute’lere karar verebiliriz
  • MFA kullanabilmekteyiz
  • Token içerisinde ihtiyaç duyacağımız claim’leri düzenleyebiliriz
  • UI‘ı markamıza göre customize edebiliriz

Gördüğümüz gibi ihtiyacımız olabilecek standart bir çok özellik user flow’lar ile esnek bir şekilde bizlere sağlanmaktadır.

User Flow Oluşturalım

Şimdi hızlıca ilk user flow’u tanımlayalım. Bunun için oluşturmuş olduğumuz Azure AD B2C instance’ına gidelim ve “Policies” menüsü altında bulunan “User flows” a tıklayalım. Ardından “New user flow” a tıklayarak “Sign up and sign in” seçeneğini seçelim. İsminden de anlaşılabileceği üzere kullanıcıların sistemimize üye olup, giriş yapabilecekleri flow’u configure edeceğiz. “Version” olarak ise recommended olanı seçelim ve flow’u aşağıdaki gibi configure edelim. Ardından oluşturacak olduğumuz flow’un ismini, daha sonra kullanmak üzere not alalım.

Burada gördüğümüz gibi MFA‘ı etkinleştirebilmekte ve kullanıcıdan almak istediğimiz bilgileri ve token içerisinde claim olarak saklamak istediğimiz bilgileri configure edebilmekteyiz. Ayrıca flow oluşturulduktan sonra da farklı configuration’lar gerçekleştirebilmekteyiz.

Örneğin attribute’leri değiştirebilir, çeşitli aşamalarda çağrılmak üzere API connector’leri ekleyebilir veya UI layout’unu değiştirebiliriz.

Örnek Bir Proje Oluşturalım

Web App İle Başlayalım

Customer-facing bir Web App geliştireceğimizi düşünelim ve geliştirme sürecini basitleştirebilmek ve hızlandırabilmek adına identity access ve management işlemlerini Azure AD B2C‘ye delege etmek istediğimizi varsayalım. Ayrıca bu Web App‘in arka planda bir Web API‘ı da güvenli bir şekilde consume etmesini istediğimizi düşünelim.

İlk olarak aşağıdaki komut satırı ile MyTodoOrgWeb adında bir .NET 7 Web App projesi oluşturalım.

dotnet new webapp -n MyTodoOrgWeb --auth IndividualB2C

Burada “auth” opsiyonunu “IndividualB2C” olarak belirttiğimiz için, .NET CLI Azure AD B2C‘yi kullanabilmemiz için gerekli olan template’i “OpenID Connect” configuration’ı ile birlikte bizim için otomatik olarak oluşturmaktadır.

Şimdi bu projeyi configure etmeden önce, ilk olarak onu oluşturmuş olduğumuz tenant içerisinde tanımlamamız gerekmektedir. Böylece authentication işlemlerini Azure AD B2C ile gerçekleştirebileceğiz. Bunun için Azure AD B2C instance’ına gidelim ve sol menüden “App registration” sekmesini seçelim. Ardından “New registration” a tıklayarak aşağıdaki gibi tanımlama işlemini yapalım.

İsim olarak buraya “MyTodoOrgWeb” verelim ve “Supported account types” seçeneğini de sonuncu olan seçeneği seçelim. “Redirect URI” olarak ise .NET CLI ile oluşturmuş olduğumuz örnek proje template’i içerisinde de default olarak gelen, “signin-oidc” callback adresini belirtelim.

App registration işlemini tamamladıktan sonra ise “Authentication” tab’ına gidelim ve OpenID Connect ile sign in işlemini hızlı bir şekilde gerçekleştirebilmek için şimdilik implicit grant flow’u “Access tokens” ve “ID tokens” seçeneklerini seçerek etkinleştirelim. Daha sonra bir Web API çağırma aşamasına geçtiğimizde ise implicit grant flow’u kapatarak, daha secure olabilmesi açısından authorization code flow‘u etkinleştiriyor olacağız. Çünkü implicit grant flow’da ilgili token’lar redirect’ler sırasında URL ‘lerin query parametreleri içerisinde iletildiği için, kolaylıkla intercept edilebilme riskleri bulunmaktadır.

Ardından “Certificates & secrets” tab’ına giderek şimdiden bir client secret tanımlayalım. Bu secret “MyTodoOrgWeb” ‘in bir sonraki aşamada oluşturacak olduğumuz Web API ‘ı güvenli bir şekilde consume edebilmesi için kullanılacak.

Şimdi ufak bir test yapabiliriz.

Öncelikle “MyTodoOrgWeb” uygulamasının “appsettings.json” dosyasını aşağıdaki gibi oluşturmuş olduğumuz bilgiler doğrultusunda güncelleyelim.

{
  "AzureAdB2C": {
    "Instance": "https://YOUR_ORGANIZATION.b2clogin.com/",
    "ClientId": "CLIENT_ID",
    "CallbackPath": "/signin-oidc",
    "Domain": "YOUR_ORGANIZATION.onmicrosoft.com",
    "SignedOutCallbackPath": "/signout/B2C_1_CreateUserFlow",
    "SignUpSignInPolicyId": "B2C_1_CreateUserFlow",
    "ClientSecret": "YOUR_CLIENT_SECRET"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Ardından “MyTodoOrgWeb” uygulamasını çalıştıralım ve “Sign in” butonuna basalım.

Bu noktada bizi yönlendirmiş olduğu “Sign up and sign in” user flow unun URL ‘i içerisine bakarsak, “response_type=id_token” query parametresinin set edildiğini görebiliriz. Yani burada sign in işlemi sonucunda bir “id_token” elde ediyor olacağız.

Gördüğümüz .NET CLI ile oluşturmuş olduğumuz Web App template’i ve Azure AD B2C user flow’ları sayesinde kolay ve hızlı bir şekilde sign up ve sign in işlemlerini gerçekleştirebilen bir yapıya sahip olduk.

Şimdi ise bu Web App üzerinden Azure AD B2C ile güvenli bir hale getirilmiş (getireceğimiz) bir Web API ‘a nasıl erişim sağlayabiliriz bir ona bakalım.

Web API Oluşturalım

Yine .NET CLI ‘ı kullanarak “MyTodoOrgAPI” adında örnek bir .NET 7 Web API projesi oluşturalım.

dotnet new webapi -n MyTodoOrgAPI --auth IndividualB2C

.NET CLI bize yine authentication ve authorization işlemleri için Azure AD B2C kullanan bir API template’i oluşturacaktır.

Ardından daha önce yaptığımız gibi projenin configuration işlemine başlamadan önce tenant içerisinde tanımlama işlemini gerçekleştirelim.

Bu sefer “Supported account types” olarak “Accounts in this organizational directory only…” seçeneğini seçelim.

App registration işlemini tamamladıktan sonra ise şimdi bu API üzerinde fine-grained bir access kontrole sahip olabilmemiz için, scope’lardan yararlanacağız. Scope’lar sayesinde bir API ‘ın farklı endpoint’lerine veya fonksiyonalitelerini olan erişimi, client’lar bazında sınırlayabilmekteyiz.

Bunun için öncelikle “Expose an API” tab’ına geçelim ve scope’lar için prefix olarak kullanılacak olan “Application ID URI” bilgisini düzenleyelim. Ben “mytodoorgapi” değerini verdim. Ardından “Add a scope” butonuna basalım ve aşağıdaki gibi “read” adında örnek bir scope ekleyelim.

Kısaca bu API ‘ı kullanıcı adına çağıracak olan client’ın, “read” scope’una sahip olması beklenecektir.

Dolayısıyla “MyTodoOrgWeb” ‘in “MyTodoOrgAPI” ‘ına erişim sağlayabilmesi için, tanımlamış olduğumuz bu scope’a erişim iznini vermemiz gerekmektedir. Bunun için Azure AD B2C üzerinden “MyTodoOrgWeb” uygulamasına gidelim ve “API permissions” tab’ına geçelim. Ardından “Add permission” butonuna tıklayalım ve “APIs my organization uses” sekmesi altından tanımlamış olduğumuz API ‘ı bularak “read” scope’unu seçelim.

Read” scope’u için erişim iznini ekledikten sonra ise, son olarak “Admin consent” ‘i vermemiz gerekmektedir. Kısacası “MyTodoOrgWeb” ‘in “MyTodoOrgAPI” ‘ına doğrudan erişebilmesi için, “Admin consent” ‘i verme işlemi gerçekleştirmemiz gerekmektedir.

Bu noktadan itibaren Azure AD B2C üzerindeki configuration işlemlerini her iki uygulama içinde tamamlamış olduk.

Şimdi ilk olarak “MyTodoOrgAPI” ‘ın kod tarafındaki düzenlemelerini gerçekleştirelim. Template ile default gelen “WeatherForecast” örneğini olduğu gibi kullanalım. Sadece controller içerisindeki “Route” ve “RequiredScope” attribute’lerini explicit bir şekilde aşağıdaki gibi güncelleyelim.

[Route("weatherforecasts")]
[RequiredScope("read")]

Böylece “read” scope’una sahip olan bir client, bu controller altındaki endpoint’lere erişim sağlayabilecektir. “Program.cs” içerisine baktığımızda da zaten API ‘ın authentication middleware’i ile hazır bir şekilde geldiğini görebiliriz.

Ardından API ‘ın da “appsettings.json” configuration dosyasını daha önce oluşturmuş olduğumuz bilgiler doğrultusunda güncelleyelim.

{
  "AzureAdB2C": {
    "Instance": "https://YOUR_ORGANIZATION.b2clogin.com/",
    "ClientId": "CLIENT_ID",
    "Domain": "YOUR_ORGANIZATION.onmicrosoft.com",
    "Scopes": "read",
    "SignUpSignInPolicyId": "B2C_1_CreateUserFlow"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Artık “MyTodoOrgAPI” ‘da Azure AD B2C authentication kullanımına hazır durumda.

Şimdi ise “MyTodoOrgWeb” tarafındaki kod düzenlemelerini gerçekleştirelim. Öncelikle “MyTodoOrgAPI” template’i ile hazır gelen “WeatherForecast” class’ını, “MyTodoOrgWeb” içerisinde de olduğu gibi tanımlayalım. Ardından “MyTodoOrgWeb” ‘in “Pages” klasörü altındaki “Index.cshtml” dosyasını aşağıdaki gibi güncelleyelim.

@page
@model IndexModel
@using Microsoft.Identity.Web
@using System.Net.Http.Headers

@inject ITokenAcquisition _tokenAcquisition
@inject IHttpClientFactory _clientFactory

@{
    ViewData["Title"] = "Home page";

    async Task<List<WeatherForecast>> GetWeatherForecastAsync()
    {
        var requiredScopeForMyTodoOrgAPI = "https://mytodoorganization.onmicrosoft.com/mytodoorgapi/read";
        var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes: new[] { requiredScopeForMyTodoOrgAPI }, user: User);


        string baseAddress = "http://localhost:5076";
        string route = "weatherforecasts";

        var client = _clientFactory.CreateClient();
        client.BaseAddress = new Uri(baseAddress);
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        var response = await client.GetAsync(route);

        if (response.IsSuccessStatusCode)
        {
            return await response.Content.ReadFromJsonAsync<List<WeatherForecast>>();
        }

        throw new Exception(response.ReasonPhrase);
    }
}

@if (User.Identity?.IsAuthenticated == true)
{
    List<WeatherForecast> weatherForecasts = await GetWeatherForecastAsync();

    <ul>
        @foreach (var weatherForecast in weatherForecasts)
        {
            <li>Date: @weatherForecast.Date.ToShortDateString()</li>
            <li>Summary: @weatherForecast.Summary</li>
            <li>Temperature: @weatherForecast.TemperatureC</li>
        }
    </ul>
}

Burada “GetWeatherForecastAsync” method’u içerisinde “MyTodoOrgAPI” ‘ını secure bir şekilde sign-in olmuş kullanıcı adına çağırabilme işlemini gerçekleştirebilmek için, “Microsoft.Identity.Web” authentication library’si ile gelen “ITokenAcquisition” servis’ini kullanıyoruz. Bu servis bizim için access token alma işlemini otomatik olarak gerçekleştirmektedir.

Bu servis ayrıca uygulamanın performansını arttırabilmek adına ilgili access token’ın cache yönetimini ve ayrıca kullanıcı session’ının geçerli kalabilmesi için ilgili access token’ın yenilenmesi gibi işlemleri de gerçekleştirmektedir.

NOT: “baseAddress” variable’ını kendi local ortamınızdaki “MyTodoOrgAPI” ‘ının adresi ile değiştirmeyi unutmayın.

Şimdi “MyTodoOrgWeb” içerisindeki “Program.cs” dosyasına gidelim ve “ITokenAcquisition” servis’inin etkinleştirme işlemini “EnableTokenAcquisitionToCallDownstreamApi” method’unu “AddMicrosoftIdentityWebApp” satırından sonra dahil ederek gerçekleştirelim. Ayrıca örnek olması açısından in-memory token cache özelliğini de aktif edelim. Production ortamları için distributed cache özelliğini kullanabiliriz.

// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(Constants.AzureAdB2C))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDistributedTokenCaches();

ITokenAcquisition” servis’i default olarak authorization code flow ‘u Web App tarafında etkin hale getirmektedir. Elbette bu servis’i kullanmadan da authorization code flow’u “MyTodoOrgWeb” tarafında etkin hale getirebilirdik. Biz bir önceki aşamada henüz bir API ‘a sahip olmadığımız için ve sign in işlemlerini kolay bir şekilde gerçekleştirebilmek adına implicit grant flow’u kullandık. Aşağıdaki OpenID Connect configuration’ı ile de Web App tarafında authorization code flow’u etkinleştirebilirdik.

services.Configure(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
   options.ResponseType = "code";
});

Dolayısıyla AzureAD B2C üzerinden “MyTodoOrgWeb” uygulaması için etkinleştirmiş olduğumuz implicit flow’u, aşağıdaki gibi her iki seçeneği de disable ederek, authorization code flow olarak değiştirebiliriz.

Artık sign in işlemleri sırasında Web App tarafında bir authrozaiton code elde ediliyor ve bu kod backend tarafında “ITokenAcquisition” servis’i vasıtasıyla bir access token elde edebilmek için kullanılıyor olacak.

Şimdi her iki uygulamayı da çalıştıralım ve “MyTodoOrgWeb” ‘in tekrardan sign in sayfasına gelelim. Yönlendirildiğimiz user flow’un URL ‘inde ise bu sefer “response_type” ‘ın “code” olarak set edildiğini görebiliriz.

Sign in işleminin sonrasında ise “MyTodoOrgWeb” ‘in authenticate olmuş kullanıcı adına geçerli bir access token alarak, “MyTodoOrgAPI” ‘ını başarılı bir şekilde consume ettiğini de görebiliriz. Ayrıca “MyTodoOrgAPI” ‘ı tarafında ise ilgili bearer token ve scope’u valide edilerek, ilgili işlem gerçekleştirilmektedir.

Referanslar

https://learn.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-api?tabs=csharpclient
https://learn.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-application?tabs=visual-studio
https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow

İlgili Makaleler

Bir Yorum

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Başa dön tuşu