IdentityServer4 Nedir ve Client Credential Örneği

Merhaba, bir önceki yazımda OAuth2 ve OpenId Connect protokolleri nedir ve nasıl yetki mekanizmaları içerdiğini paylaşmıştım. Bu yazımda bu protokolleri kullanan, .Net Core ile kullanabildiğimiz, açık kaynak kodlu IdentityServer4 Framework’ü ve   Client Credential Grant Type örneğini, Client, Api ve IdentityServer bazında paylaşmaya çalışacağım.

Geliştirdiğimiz uygulamanın güvenliği ve uygulamanın kullandığı verilerin korunması ve yönetilmesi oldukça önemlidir. Gelişen teknoloji ile farklı teknolojilerde uygulama veya cihazların verilerimize erişmesi, bizler için hem güvenlik hem de yazılımsal olarak bazı problemleri veya ek yükleri olabilir. Örneğin geliştirdiğimiz bir Web Api’ı, kullanacak bir SPA(Single Page App) Client uygulamanın kullanıcıları, sisteme kullanıcı adı ve şifre ile giriş yaptığını düşünelim. Daha sonra aynı Web Api’mize bir başka bir client web uygulaması windows authentication ile doğrulama yapıp, girmesi istendiğini,  bir native mobile  client  uygulamanın erişmek istediğini veya başka bir web api’nin geliştirdiğimiz Web Api’ı erişmek istediği zaman, her client uygulamanın desteklediği güvenlik teknolojisi için uygulamamızın güvenlik kısmında geliştirme veya yapılandırma yapmamız gerekebilir. Böyle bir durumda aynı kullanıcı grupları için client uygulamalar ve veri kaynaklarının erişimi ve güvenliği için dinamik bir şekilde client ve resource (api) bazında merkezi bir (Authentication/Authorization Server) uygulamasına ihtiyacımız bulunmaktadır.

IdentityServer4 Nedir?

Identity Server4, .Net Core için  OpenId Connect ve OAuth2 protokolleri kullanan açık kaynak kodlu bir framework’dür. IdentityServer ile web uygulamalarımız veya Web Api’lerimiz için tek bir noktadan uygulamalar arası veya kullanıcı bazında kimliklendirme ve erişim kontrolü sağlayabiliriz. Kurumsal uygulamalarda veya modern web uygulamalarında Web Api veya Api’lere (Resource) erişmek isteyen web, javascript veya native client uygulamaların nasıl bir doğrulama ve yetkilendirme kullanarak erişebileceklerini IdentityServer belirler. Bunun için Web api tarafında client uygulamasına özgü kimliklendirme yazmaya gerek kalmaz.

IdentityServer4 ile neler yapabiliriz ?

Doğrulama Servisi :  Tüm uygulamalarımız( web, mobil, native, service ) için merkezi giriş (login) mantığı ve akışı oluşturabiliriz.

Single Sign on / Single sign out  :  Bir çok uygulama türü için single sign on-out işlemini gerçekleştirebiliriz.

Web Api İçin Erişim Kontrolü:  Geliştirdiğimiz web api’lerimizi kullanacak bir çok client ve server uygulama için erişim kontrolllerini sağlayabiliriz.

Federation Gateway: Azure Active Directory, Google, Facebook gibi uygulamaları harici kimlik sağlayıcı uygulamalara desteği bulunmaktadır. Harici sağlayıcılara kolay bir şekilde bağlanabiliriz.

Özelleştirme: IdentityServer ‘in bir çok kısmını ihtiyaçlarımıza göre özelleştirebiliriz.

Kullanılan Terminolojiler

Temelde yapımızı, client uygulamalar ( web, native, mobile ) , veri kaynağı uygulamalar (web api, service) ve IdentityServer uygulaması olarak düşünebiliriz.

Client Uygulamaları, kullanıcıların kullandıkları güvenli veri kaynaklarına (Web Api) erişmek isteyen uygulamalardır.

 Veri Kaynakları (Resources) , IdentityServer tarafından korunmasını istediğimiz datalardır. Veri kaynakları tekil bir isme sahip olmalıdır ve bu kaynağı kullanacak client, bu isim ile kaynağa erişim sağlamalıdır.

IdentityServer, OpenId Connect sağlayıcı ve OAuth2 ve OpenId protokollerini kullanır. Kısaca client uygulamalara güvenlik tokenlarını sağlar. IdentityServer, veri kaynaklarını korur, kullanıcıların doğrulanmasını sağlar, single sign-on ve session yönetimini sağlar, client uygulamaların doğrulanmasını sağlar, client uygulamalara identity ve access token sağlar ve bu token’ların doğruluğunu kontrol eder.

Identity Token,  kimlik doğrulama işlemi sonucunu ifade eder. Kullanıcı için bir alt tanımlayıcı ve kullanıcı nasıl ve ne zaman doğrulanacağı hakkında bilgiler içerir. Ayrıca kimlik (Identity) bilgileri içerir.

Access Token, veri kaynağına (Api) erişimi sağlar. Client uygulaması veri kaynağına bu token ile istek göndererek verilere erişebilir. Access token client ve kullanıcı bilgileri içerir. Api bu bilgileri kullanarak yetkilendirme (authorization) yapar ve verilere erişime izin verir.

IDENTITY SERVER 4 Projemizin Oluşturulması

IdentiyServer için yeni bir .net core projesi oluşturuyorsanız, IdentityServer tarafından oluşturulmuş proje şablonlarını kullanabilirsiniz.Bu şablonları kullanmak için komut istemine aşağıdaki kodu girip tüm şablonları indirip kullanabilirsiniz. Eğer hali hazırda bir projenizi IdentityServer olarak geliştirmek isterseniz, IdentityServer4 nuget paketlerini uygulamanıza dahil edebilirsiniz.

Identity server 4 template yüklemek için

dotnet new -i IdentityServer4.Templates

komutunu windows komut istemine yazıyoruz. Bu sistemimize IdentityServer şablonlarını yüklememizi sağlar. Böylece şablonları geliştirme ortamlarımız için kullanabiliriz.

IdentityServer4 Template

Örnek olarak Asp.Net Core Identity içeren şablonu kullanarak Asp.Net Core kullanıcı, rol ve login mantığını kullanalım. Projemizi oluşturacağımız  klasör içerisine komut istemi ile gidelim ve dotnet new is4aspid –n “Projemizin Adı” ben AuthServerIds4 ismini verdim. Siz istediğiniz bir ismi verebilirsiniz. Bu komutla birlikte IdentityServer kütüphanelerini içeren, Asp.Net Core Identity yapısını içeren ve IdentityServer4 tarafından hazırlanmış login, consent sayfalarını içeren web projesi oluşmuş olacaktır.

İkinci adım olarak projemiz için bir solution oluşturup, IdentityServer projemizi bu solution bağlamamız gerekmektedir. Bunun için ilk önce dotnet new sln –n “Solution Adı” komutu ile yeni bir solution oluşturuyoruz. Şimdi  daha önce oluşturduğumuz projemizi solution’a eklememiz gerekir. Bunun için dotnet sln add “Proje path.csproj” komutu ile ekliyoruz. Şimdi solution visual studio ile açıp projemizi inceleyebiliriz.

Template ile oluşturduğumuz IdentityServer uygulaması, Login, logout, consent gibi işlemler için controller ve view’lerden oluşan bir mvc projesidir. İlk olarak appsettings.json dosyasında Connection string kısmını kendi local veri tabanı connection string ile değiştirip, migration’ı uygulamamız gerekli. Fakat IdentityServer template, sqlite ile configure edilmiş şekilde gelmektedir. SqlServer kullanmak için Microsoft.EntityFrameworkCore.SqlServer  referans paketini uygulamamıza nuget ile eklememiz ve UseSqlite methodlarını UseSqlServer  ile değiştirmemiz gerekmektedir.  Migration ile Asp.Net Identity için belirlenmiş User, UserClaim, Role, UserRole gibi tabloları local veri tabanımıza oluştururuz.

Projeyi derleyip çalıştırdıktan sonra http://localhost:5000/.well-known/openid-configuration url ‘den identityserver için kullanabileceğimiz endpoint ve desteklenen identity type bilgilerine erişebilirsiniz.

IDENTITYSERVER CONFIGURATION

Uygulamamızın dosyalarını incelersek, Config.cs dosyası Resources(Api), client ve IdentityResource  tanımladığımız kısımdır.

Resources veya Apis korumak istediğimiz veri kaynağı veya web servisimizin tanımlandığı kısımdır. Bunun için bu dosyada bulunan Apis methodu ( eski versiyonda GetApis) kullanılmaktadır. Bu method IEnumerable ApiResource nesnesi dönmektedir. Korumak istediğimiz tüm api bilgilerimizi bu method içerisinde tanımlamalıyız.

Clients ( eski versiyonda GetClients ) kısmında Api resource’e erişmesi gereken client uygulamaların tanımlamalarının yapıldığı metottur. Her client uygulama, IdentityServer tarafında Client nesnesinde tanımlanır. Bu method client nesnelerinin listesini döner.

Ids (eski versiyonda GetIdentityResources) IdentityResource tanımlandığı kısımdır.  IdentityResource, userId, email, name gibi kullanıcı bilgilerini içeren, unique bir isme sahip ve bunlara bağlı claim türlerini atayabildiğimiz bilgilerdir. Bir kullanıcı için tanımlanmış Identity Resource bilgileri, identity token içerisine dahil edilir. Client ayarları içerisinde scope  parametresi ile tanımlamış olduğumuz Identity Resource’ları kullanabiliriz. Identity Resource subject id olarak adlandırılan kullanıcı için belirlenmiş bir unique id en az bir tane tanımlanmalıdır.

Config sınıfı uygulamanın Startup kısmında IdentityServer konfigurasyonunda inmemory olarak kullanılır.

Bir uygulamanın IdentityServer olarak kullanmak için Startup sınıfında ConfigureServices methodu içerisinde aşağıda tanımlama yapılmalıdır.

var builder = services.AddIdentityServer(options =>
              {
                  options.Events.RaiseErrorEvents = true;
                  options.Events.RaiseInformationEvents = true;
                  options.Events.RaiseFailureEvents = true;
                  options.Events.RaiseSuccessEvents = true;
              })
              .AddInMemoryIdentityResources(Config.Ids)
              .AddInMemoryApiResources(Config.Apis)
              .AddInMemoryClients(Config.Clients)
              .AddAspNetIdentity<ApplicationUser>();

Microsoft Identity ile kullanıcı role ve login yapısı kullanmak için ConfigureServices methoduna aşağıdaki kod eklenmelidir.

services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

Startup dosyası içersinde Configure methodu içersinde IdentityServer4 uygulamamıza eklenmesi için app.UseIdentityServer() methodunun eklenmesi gerekmektedir.

IdentityServer olarak oluşturduğumuz web uygulamamızın https olarak yapılandıralım ve diğer uygulamalarımızın yapılandırmasında https’li adresi kullanalım. Bunun için uygulamanın Properties kısmında launchsettings.json dosyası içersinde iisExpress json yapısı altınd sslPort kısmını 443 ile başlayan herhangi bir port numarası verelim. Böylece uygulamamız https olarak belirlediğimiz port numarası ile çalışacaktır.

Quickstart klasörü altında Account klasörü içerisinde AccountController dosyası uygulamamızın Login, Logout  işlem ve sayfa methodlarını içerir. ExternalController, external login işlemlerini ( Windows, Google gibi ) içeren methodları ve sayfaları içeren kısımdır. Consent ve Grant dosyaları kullanıcı izinleri için oluşturulmuş method ve sayfaları içermektedir.

CLIENT CREDENTIAL İLE MACHINE TO MACHINE KİMLİKLENDİRME

Client credential yetki tipini kullanarak IdentityServer Config.cs dosyamızı yapılandıralım. Client credential sadece client doğrulaması ile erişimi sağladığı için machine to machine kimliklendirme için kullanılmalıdır. Yani bu akış türünü iki uygulama arasında etkileşime ihtiyaç duyduğumuz zaman kimliklendirme ve yetki mekanizması olarak kullanmalıyız. Örneğin bir web api uygulamamızın geliştirdiğimiz başka bir web api veya web uygulaması ile entegre etme gibi. Sonuç olarak kullanıcı kimliklendirmesi uygulamamız için önemli değilse ve sadece client uygulamamızın doğrulanması yeterli ise tercih edebiliriz.

Config.cs dosyamıza ilk olarak korumak istediğimiz api uygulamamızı apis methoduna ekliyoruz.

public static IEnumerable<ApiResource> Apis =>
            new ApiResource[]
            {
                new ApiResource("clientcreapi", "Client Credential API")
            };

ApiResource nesnesi olarak ilk parametre tekil (unique) bir isim ve ikinci parametre display name olarak verilir. Verdiğimiz bu ismi daha sonra resource olarak kullanmak istediğimiz web api Startup.cs dosyasında Authentication ayarlarına ekleyeceğiz.

Api Resource (Web Api) Uygulamamızın Oluşturulması

IdentityServer uygulamamızı, test edebilmek için solution’a yeni bir web api projesi ekliyoruz. Web api uygulamasını isterseniz ayrı bir solution içerisinde oluşturabilirsiniz veya daha önce oluşturduğunuz bir web api  yapılandırabilirsiniz.

Api uygulaması için Authentication ve Jwt Bearer kullanabilmek için Microsoft Authentication JwtBearer extension paketini nuget ile kurmamız gerekmektedir. Ayrıca .csproj dosyamızın içersinde bulunan <itemgroup></itemgroup> tag’i içersine aşağıdaki paket bilgisini içeren kodu ekleyebilirsiniz.

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.0" />

Web api uygulamanızı  https olarak çalıştırmak istiyorsanız, solution Properties kısmında launchsettings.json dosyası içersinden sslPort  kısmına 443 ile başlayan bir port numarası belirtmemiz gerekiyor.

Api uygulamasının, authentication ve authorization için IdentityServer uygulaması ile etkileşim kurması için Startup.cs dosyası içersinde yapılandırmamız gerekmektedir. Startup.cs dosyası içerisinde bulunan  ConfigureServices fonksiyonu içerisine varsayılan Bearer şeması ve JwtBearer yapısını kullanabilmek için aşağıdaki kodları yazıyoruz.

services.AddAuthentication("Bearer")
                .AddJwtBearer("Bearer", options =>
                {
      // IdentityServer uygulamasının adresi
                    options.Authority = "https://localhost:44366";
                    options.RequireHttpsMetadata = true;

                    options.Audience = "clientcreapi";
                });

Authentication ve Authorization’u, Api uygulamamızda kullanabilmek için uygulamanın Http request pipeline’a  eklememiz gerekmektedir. Bunun için Configure methodu içerisine app.UseAuthentication() ve app.UseAuthorization() metotlarını aşağıdaki gibi eklemeliyiz.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
      {
          if (env.IsDevelopment())
          {
              app.UseDeveloperExceptionPage();
          }

          app.UseHttpsRedirection();
          app.UseRouting();
          app.UseAuthentication();
          app.UseAuthorization();

          app.UseEndpoints(endpoints =>
          {
              endpoints.MapControllers();
          });
      }

Api uygulamamıza test amaçlı Identity adından bir controller ekleyelim. Bu controller sınıfımızın en üstününe Authorize attribute ekleyelim. Böylece bu controller’a sadece authenticate olan kullanıcı erişebilsin.  Ardından Get methoduna Authenticate olan kullanıcının claim bilgilerini listeleyen aşağıdaki kodu ekleyelim. Sonuç olarak IdentityController aşağıdaki gibi olacaktır.

[Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class IdentityController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
           return new JsonResult(from c in User.Claims select new { c.Type, c.Value});
        }

    }

Identity Server Client Uygulama Yapılandırması

Şimdi web api uygulamamızı kullanacak veya erişecek Client uygulaması için IdentityServer uygulamasında Client tanımlamamızı yapmamız gerekmektedir. Bunun için Config.cs dosyamızın içerisinde GetClients  veya Clients metoduna aşağıdaki şekilde tanımlamamız gerekmektedir.

new Client
                {
                    ClientId = "client",
                    ClientName = "Client Credentials Client",

                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets = { new Secret("clientCredentialSecret".Sha256()) },

                    AllowedScopes = { "clientcreapi" }
                }, 

Client nesnesinin özelliklerine bakarsak, ClientId tanımlanan her client için belirlediğimiz string tekil bir Id olmalıdır. Bu özellik daha sonra client uygulamamızın OpenId yapılandırılmasında kullanılacaktır. ClientName  IdentityServer tarafında client uygulaması için bir açıklama alanı olarak düşünebiliriz. AllowedGrantTypes özelliği tanımladığımız client uygulamanın hangi akışı kullanacağını belirtir. IdentityServer tarafında tanımlı akış tiplerini kullanarak atama yapabiliriz. ClientSecrets, Secret nesnesini içeren bir listeyi ataya bildiğimiz bir özelliktir. Secret nesnesi ile gerekli olan akışa göre client doğrulaması yapmak için kullanılacaktır. AllowedScopes özelliği client uygulamamızın hangi scope’lara erişebileceğini belirttiğimiz özelliktir. Burada tanımlanan api ve identity resources’lar birer scope’tur ve burada belirterek client uygulama üzerinden erişebiliriz. Ayrıca Identity Server tarafında scope üzerinden client doğrulaması yapılmaktadır.

Client Uygulama ( Client Credential )

Client Credential akışında kullanıcı yerine uygulama doğrulaması yapılır. Bu akış daha çok iki uygulamanın iletişiminde veya kullanıcı bazlı olmayan bir client uygulamada doğrulama ve izin yapısı için kullanılır.

Api uygulamamıza erişecek olan client uygulamamızı Mvc veya Console app olarak ekleyebiliriz. Mvc olarak bir client uygulama ekleyelim. IdentityServer’de token endpoint, OAuth2 protokolünü kullanır ve Http ile erişilebilir. Client uygulamamızın, client credential akışını kullanarak bir IdentityServer  token endpoint’e kolaylıkla etkileşim kurabilmesi için IdentityModel kütüphanesini kullanabiliriz. Nuget manager ‘dan IdentityModel’i arayıp, projemize ekleyelim.  HomeController içerisine CallApi adında yeni bir metot ekleyelim. Bu metot oluşturduğumuz api’mizin IdentityController’ına get metodu ile ulaşıp sonucu bir sayfada json olarak gösterecektir.

public async Task<IActionResult> CallApi()
        {
            // Identity Model Client Configuration and discovery in the Ids
            var client = new HttpClient();
            var disco = await client.GetDiscoveryDocumentAsync("https://localhost:44366");
            if (disco.IsError)
            {
                //throw new ApplicationException(disco.Error);
                return Json(disco.Error);
            }
            // Get Token from Ids
            var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
            {
                Address = disco.TokenEndpoint,

                ClientId = "clientcredentialId",
                ClientSecret = "clientCredentialSecret",
                Scope = "clientcreapi"
            });

            if (tokenResponse.IsError)
            {
                return Json(tokenResponse.Error);
            }
            //Request to api with access token
            client.SetBearerToken(tokenResponse.AccessToken);
            var response = await client.GetAsync("https://localhost:44367/api/identity");
            var content = await response.Content.ReadAsStringAsync();

            ViewBag.Json = JArray.Parse(content).ToString();
            return View("json");
        }

CallApi metodunda IdentityServer token endpoint ulaşmak ve api istekte bulunmak için HttpClient nesnesi oluşturuyoruz. Daha sonra IdentityModel kütüphanesinin sağladığı IdentityServer’ın token end pointlerini belirlemek için kullandığımız GetDiscoveryDocumentAsync metodu ile discovery nesnesi oluşturuyoruz. Bu metod parametre olarak IdentityServer’ın adresini almaktadır.

Şimdi token elde etmek için IdentityServer tarafında Config.cs dosyası üzerinde tanımladığımız ClientId, ClientSecret ve scope özellikleri ile IdentityServer’a clientcredential akışı üzerinden token isteğimizi gönderelim. Bunun için RequestClientCredentialTokenAsync mehoduna parametre olarak ClientCredentialTokenRequest nesnesini gönderek yapıyoruz. Dikkat ederseniz bu nesnenin özellikleri IdentityServer tarafında tanımladığımız client özellikleri ile aynı, IdentityServer bu bilgiler ile client uygulamanın doğrulamasını yapıp, token bilgisini dönmektedir.

En son olarak elde ettiğimiz access token ‘ı Api yapacağımız isteğin header’ina SetBearerToken methodu ile ekliyoruz. Api ‘dan dönen sonucumuzu bir ViewBag atıyoruz.

Ben json adında bir view oluşturdum ve sonucu bu sayfada gösterdim.

Örnek uygulama için https://github.com/muratguven/IdentityServerSamples adresinden indirebilirsiniz.

Sonuç;

Sonuç olarak, bu yazımda IdentityServer4 şablonunu ve kullanımını, IdentityServer’ın kullandığı OpenId ve OAuth2 protokollerini ve ilk örnek olarak ClientCredential akışının nasıl oluşturulduğunu ve api,client uygulamalarının nasıl yapılandırıldığını sizin paylaştım. Daha sonra ki yazımda IdentityServer üzerinden window authentication ve form authentication nasıl yapabileceğimizi hem Mvc client hemde SPA client olarak nasıl oluşturabileceğimizi anlatmaya çalışacağım.