Asp.NET Core İle REST API Üzerinden Temel Keycloak İşlemleri #3
Merhaba,
Bu içeriğimizde, uygulamalarımız üzerinden Keycloak’u nasıl yönetebileceğimizi, en azından realm, client, scope, role vs. yönetimleri gibi bazı temel seviyede admin/management temelli işlemlerin nasıl gerçekleştirilebileceğini değerlendiriyor olacağız.
Neden REST API üzerinden?
Keycloak, mimarisel olarak yalnızca bir kimlik doğrulama aracı değil, aynı zamanda merkezi bir Identity Provider (IdP) ve yetkilendirme sunucusudur. Bu nedenle Keycloak ile yapılan işlemler temelde; uygulamanın çalışma anında ihtiyaç duyduğu kimlik doğrulama akışları ve Keycloak’ın kendisinin yönetildiği ve konfigüre edildiği yönetimsel işlemler olmak üzere iki farklı düzleme ayrılmaktadır. Bu ayrım, entegrasyon yaklaşımının neden REST API üzerinden gerçekleştirildiğini anlamak açısından kritik arz etmektedir.
Şöyle ki, uygulamanın çalışma zamanında (runtime) ihtiyaç duyduğu işlemler; token doğrulama, kullanıcının oturum açıp açmadığının kontrolü ve token içindeki rol ya da scope bilgilerine göre yetkilendirme gibi gereksinimlere sahipse eğer, bu alanlarda Asp.NET Core, OAuth 2.0 ve OpenID Connect gibi standart protokolleri doğal olarak desteklemesinden ve Keycloak’ın da bu protokollere tam uyumlu bir kimlik sağlayıcı olarak davranış sergileyebilmesinden dolayı, bu seviyede geliştiricinin doğrudan REST API üzerinden Keycloak ile iletişime geçmesine gerek kalmaksızın, framework’ün sunduğu yerleşik mekanizmalardan istifade ederek rahatlıkla çözüm getirebilmektedir.
Örneğin; Asp.NET Core’un, JWT / OpenID Connect ile doğrudan authentication işlemlerini aşağıdaki gibi yürütebilmesi, bu duruma karşın anımsanmaya değer bir örnektir :
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://keycloak-server/realms/{realm-name}";
options.Audience = "...client id...";
options.RequireHttpsMetadata = true;
});
Ayrıca Authorize attribute’u ya da .RequireAuthorization(...) metodu ile korumalı endpoint’leri rahatlıkla oluşturabilmekte ve herhangi bir manuel REST çağrısına gerek kalmaksızın yetkilendirme süreçlerini rahatlıkla var olan altyapılar sayesinde gerçekleştirebilmektedir.
Ancak; kullanıcı oluşturma, rol tanımlama, client scope yapılandırma, protocol mapper ekleme ya da bir kullanıcıyı gruplarla ilişkilendirilme gibi işlemler tamamen farklı bir düzlemdedir. Bu işlemler, bir uygulamanın iş mantığının parçası değil; doğrudan Keycloak’ın kendisinin yönetilmesi anlamına geldiğinden burada Asp.NET Core’da hazır bir altyapı söz konusu değildir. Bizler bu noktada Keycloak Administration REST API‘lardan istifade etmekteyiz.
Şunun farkına varmak gerekir ki, Keycloak’ta kullanıcı, rol, scope, client ve grup gibi kavramlar token üretim sürecinin girdilerini belirleyen konfigürasyonlar olduğu için Keycloak’ın merkezi kimlik altyapısını doğrudan etkilemektedirler. Bu nedenle Keycloak, bu kavramlara dair tüm yapılandırıcı davranışları REST API üzerinden sunmakta ve /admin/realms/{realm}/… şablonu altındaki endpoint’lerle vermektedir.
Yaklaşımın böyle bir davranışa (REST API usulüne) sahip olması oldukça yerindedir çünkü; Keycloak, herhangi bir uygulamanın -iç davranışının- bir parçası değildir! Aksine bağımsız bir kimlik altyapısı sunan bir servistir/platformdur. Bundan kaynaklı bu servisin, farklı teknolojilerden, farklı dillerden ve farklı ortamlardan erişilebilecek kadar evrensel olması gerekmektedir. REST API, bu noktada en doğal ve nötr çözümdür diyebiliriz.
Keycloak’ın, yönetimsel işlemler için sağladığı resmi bir .NET Admin SDK’sı yoktur!
Bu yaklaşımın avantajları olsa da ciddi sistemlerde dezavantajları da göz ardı edilemez tabi. Özellikle, Keycloak sürüm güncellemeleriyle birlikte API’lerde yapılan değişiklikler ister istemez geliştirilen kodun geride kalmasına neden olabilmekte ve olası hata durumlarında da endpoint soyutlaması nedeniyle problemin neyden kaynaklı olduğunun anlaşılması zorlaşabilmektedir. Ancak bu olası durumların mevzu bahis olması, REST API’lerle çalışmanın hiçbir zaman eksik ya da geçici bir çözüm olduğu anlamına gelmemektedir.
Velhasıl… Artık neden REST API odaklı çalışma sergileneceğini anladığımıza göre Keycloak’da yönetimsel açıdan kritik olabilecek REST işlemlerini ele almaya başlayabiliriz.
Başlarken
İlk olarak bir Asp.NET Core projesi oluşturalım ve REST süreçlerini daha kurumsal bir şekilde yönetebilmek için aşağıdaki temel yapılandırmaları sağlayalım:
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.AddHttpClient("keycloak", configure =>
{
configure.BaseAddress = new Uri("http://127.0.0.1:8080");
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
app.Run();
Burada, görüldüğü üzere Microsoft.AspNetCore.OpenApi kütüphanesinden istifade ederek API tanımlarını otomatik oluşturmakta ve Scalar.AspNetCore kütüphanesiyle de bu oluşturulan API tanımlarını görselleştirmekteyiz.
Ayrıca AddHttpClient yapılandırması ile ‘keycloak’ ifadesine karşın temel url tanımı gerçekleştirilmekte ve yapacağımız çalışmalara IHttpClientFactory‘i kullanabileceğimiz bir altyapı sağlanmaktadır.
Tüm bunların dışında, testlerimizi hızlı bir şekilde scalar arayüzünden gerçekleştirebilmek için launchSettings.json dosyası üzerinden launchUrl ayarını aşağıdaki gibi ekleyelim:
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
.
.
.
"https": {
.
.
.
"launchUrl": "scalar",
}
}
}
Böylece uygulama derlenip çalıştırıldığı vakit direkt tarayıcıda scalar arayüzü bizleri karşılıyor olacaktır.
Evet, artık çalışmalara teknik olarak hazırız diyebiliriz.
.well-known İle Endpoint’leri Keşfedelim
Keycloak’taki
.well-knownendpoint’i, OpenID Connect Discovery standardını uygulayan ve bir realm’e ait tüm OAuth/OIDC endpoint’lerini, destekleyen akışları ve güvenlik yapılandırmalarını tek bir JSON doküman üzerinden sunan bir keşif mekanizmasıdır. Bu yapı sayesinde istemci uygulamalar, manuel konfigürasyona ihtiyaç duymadan Keycloak ile güvenli ve standartlara uygun bir şekilde entegre olabilmektedirler.
Keycloak’ta .well-known, OAuth 2.0 / OpenID Connect standartlarında tanımlı bir keşif (discovery) mekanizmasıdır. Bu mekanizma sayesinde bizler, uygulama üzerinden Keycloak’a dair tüm endpoint adreslerini, desteklediği akışları ve yetenekleri otomatik olarak öğrenebilmekteyiz.
Böylece;
- Authorization endpoint’i hangisi?
- Hangi grant type’lar destekleniyor?
- Public key’leri nereden alabilirim?
- Issuer değeri ne a*?
- Token endpoint’i nerede layn!
gibi soruların tek bir yerden cevabı sağlanabilmektedir.
Keycloak’ta OpenID Connect için kullanılan adres şablonu aşağıdaki gibidir;
Eğer bu adres tarayıcıda açılırsa aşağıdaki gibi bir JSON dokümanıyla karşılaşılacaktır:
Şimdi bizler basitleştirerek bazı kritik alanları ayıklayarak, izah etmeye çalışalım;
- issuer : Token’ın kim tarafından üretildiğini söylemektedir.
-
authorization_endpoint : Login endpoint’idir.
token_endpoint : Token alma endpoint’idir.
userinfo_endpoint : Token doğrulama endpoint’idir.
end_session_endpoint : Logout endpoint’idir.
jwks_uri : Public key alma endpoint’idir. Access token’ların imzasını doğrulamak için bu public key’lere ihtiyacımız olacaktır. - grant_types_supported : Bu realm’de hangi OAuth akışlarının desteklendiğini gösterir.
-
scopes_supported : Desteklenen scope’lar.
response_types_supported : Desteklenen response type’lar.
token_endpoint_auth_methods_supported : Desteklenen client authentication yöntemleri.
.well-known, esasında OAuth 2.0/OpenID Connect standardı olduğu için Asp.NET Core mimariside özünde bu kavrama uygun davranış sergileyerek bazı bilgilere otomatik erişim sağlayabilmektedir. Şöyle ki, hatırlarsanız yukarıdaki satırlarda şu authentication işlemini örnek vermiştik:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://keycloak-server/realms/{realm-name}";
options.Audience = "...client id...";
options.RequireHttpsMetadata = true;
});
Şimdi bu tarz bir çalışma neticesinde Asp.NET Core mimarisi davranışsal olarak kendiliğinden,
adresine gidecektir ve bu adresten otomatik olarak token endpoint’ini, issuer bilgisini ve jwks_uri değerini çekecektir. Ve böylece geliştirici açısından hiçbir manuel endpoint tanımlaması gerekmeyecektir!
Normal şartlarda bizler .well-known mekanizmasını kullanarak Keycloak’daki lazım olan bazı endpoint’leri programatik olarak elde edebilirdik. Ancak bu içeriğimizde bu kısımla uğraşmayacak ve gerektiği noktada, gereken endpoint’i manuel kopyalayıp sadece ilgili çalışmaya odaklanıyor olacağız.
Hadi yavaştan ilerlemeye başlayalım…
Client Credentials İle Access Token Alma
Her şeyden önce Keycloak’u uygulama tarafından programatik olarak yönetebilmek için client credentials yaklaşımıyla bir access token almamız ve böylece uygulamayı yetkilendirebilmemiz gerekmektedir.
Tabi burada akla gelebilecek ilk soru şudur : “Biz hangi realm üzerinden access token talep edeceğiz?” Eğer ki, geliştirilen uygulama ile yapılacak çalışmalarda Keycloak’ın yönetimini sağlayacaksak master realm üzerinden token talebinde bulunulacaktır. Çünkü master realm, adı üzerinde yönetim realm’idir ve diğer realm’leri oluşturma, silme, yönetme vs. gibi yetkilere sahiptir. Yani master realm, ‘root/system realm’ olarak nitelendirilebilir.
Yok eğer realm seviyesinde çalışma yapmayacak ve sadece tek bir realm içerisinde kullanıcı oluşturma, client yapılandırma vs. gibi alt seviye işlemler yürütülecekse ilgili realm’den access token talep edilecektir.
Bu doğrultuda akla şu soru da gelebilir : “Bizler bulunduğumuz herhangi bir realm içerisinde access token’ı hangi client üzerinden alacağız?” Bu suale cevap olarak akıllara bulunulan realm’in içerisindeki admin-cli client’ı gelebilir. Ancak bu pek doğru bir düşünce değildir. Çünkü admin-cli bir public client‘tır. Yani client_secret değeri olmadığı için client credentials grant’ini desteklememekte, password grant‘i desteklemektedir. Haliyle bizlerin client credentials akışıyla uygulamaya yetki aldırabilmemiz için kendimiz tarafından oluşturulan confidential + service account bir client’a ihtiyacımız olacaktır.
Bu client’ı, istersek master realm‘deki admin kullanıcısıyla, admin-cli client’ından elde edilen token ile programatik oluşturabilir, istersek de bir kereye mahsus Keycloak Dashboard’u üzerinden manuel oluşturabiliriz. Artık bu bizlerin ve sizlerin tercihi… Bizler şimdilik bu client’ı manuel oluşturmayı tercih ederek yolumuza devam edeceğiz.
Bunun için aşağıdaki ayarlarda ve restapi-playground adında bir confidential client oluşturalım:
Buradaki yapılandırmayı izah etmemiz gerekirse eğer; Client authentication ile Service account roles alanlarını seçerek istediğimiz tarzda ve yeteneklerde yeterli bir client oluşturmuş oluyoruz. Ayrıca Authorization davranışının açılmasına ve Standard Flow‘unda seçili olmasına gerek olmadığını ifade etmek isterim. Lakin bu özellikler açılsa da bir zararı olmayacaktır, lakin amaç yalın olarak buradaki bahsi konuysa, bunların devreye alınmaması gereksiz karmaşıklık yaratmamak adına daha doğru olacaktır. Ama burada özellikle dikkat edilmesi gereken bir nokta vardır ki, o da, Direct access grants‘ın kesin kapalı olması gerekliliğidir. Çünkü bizler burada username / password’den uzak, sadece client credential yaklaşımını uygulayacağız…
Evet, artık elimizde bir confidential client bulunmaktadır. Artık bu client üzerinden aşağıdaki gibi client credentials flow ile access token talebinde bulunabiliriz:
app.MapGet("/get-token", async (IHttpClientFactory httpClientFactory) =>
{
var httpClient = httpClientFactory.CreateClient("keycloak");
var formData = new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "restapi-playground",
["client_secret"] = "TSQ7dhxgymeqxPunV18S3WQY6dmBY9Il"
};
var content = new FormUrlEncodedContent(formData);
var response = await httpClient.PostAsync("/realms/master/protocol/openid-connect/token", content);
var result = await response.Content.ReadFromJsonAsync<AccessTokenResponse>();
return TypedResults.Ok(result);
});
Dikkat ederseniz burada access token edinebilmek için;
endpoint’i kullanılmaktadır. Bu endpoint yapısal olarak; password, client_credentials ve authorization_code akışlarının hepsini desteklemektedir. Ayrıca, oluşturduğumuz client’ın client_secret değerine erişim gösterilebilmesi için dashboard üzerinden ilgili client’ın ‘Credentials’ sekmesine göz atılması yeterli olacaktır.
Velhasıl… Örnek bir istekte bulunulduğu taktirde aşağıdaki gibi bir sonuçla access token elde ediliyor olacaktır:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvOXh1TGJVakZrdGZlbk1UeGoycFYyaHQyYmZTZHBEYkhxZ3I5V3hxQnUwIn0.eyJleHAiOjE3Njg1NTIyOTUsImlhdCI6MTc2ODU1MjIzNSwianRpIjoidHJydGNjOmZhNDhmNTk0LWIxNmMtZGJlNC1lODI4LTExYjczNDkwYzc4NSIsImlzcyI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImM1MjFmNzU0LTdiNGEtNGFiOS04NjQ3LTIwOGUxZGU5MDUwZSIsInR5cCI6IkJlYXJlciIsImF6cCI6InJlc3RhcGktcGxheWdyb3VuZCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtbWFzdGVyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjE3Mi4xNy4wLjEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtcmVzdGFwaS1wbGF5Z3JvdW5kIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4xNy4wLjEiLCJjbGllbnRfaWQiOiJyZXN0YXBpLXBsYXlncm91bmQifQ.EsKbaxpbWEDhkHaqRNeZe7NcBGAtD1htd74_X9U_rnnlWRspCSKePQK3aEkX8RovjuNPsoB4F3ohw4tS4ndT-JomhwrROPmQBllwZJU0ZpAs6mMqmhzNeTfF6yIsYoLB3DbDNtP6_NIGcsJzKxDE1xAaiikFtQUkIDBG-rMDyRSoweFjAvqo474cSpV0qpPgUtC3Fu6pXqI4i1MyFiWcLgpLGGnKlEMmnNCwusJmdNg16mIBolSkRQvhj2ECMJ7D1rQ2drNA6f0N0rEO3l2uWwVcHmbcvqj4DddFv-9yh-9GKosrPKQ1kqq7pwlG1AIP9X66DSC10SQkg_Oc0CQgVA",
"expires_in": 60,
"refresh_expires_in": 0,
"refresh_token": null,
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": null,
"scope": "email profile"
}
Şimdi bizlere birçok çalışmada eşlik edecek uzun süreli bir access token’a ihtiyacımız vardır. Bunun için Keycloak Dashboard’un ‘Configure’ alanından ‘Token’ sekmesine gelip, ‘Access Token Lifespan’ ayarını 10 gün olarak ayarlayıp yeni bir token üretmemiz yeterli olacaktır. Ya da yapacağımız her işlem için yukarıdaki endpoint’i tekrar tekrar kullanarak sürece odaklanabiliriz. Buradaki token ihtiyacının gideriliş usulünü sizlere bırakıyorum.
Kritik Yönetimsel REST İşlemleri
-
Realm Yönetimi
Realm yönetimi; Keycloak’ın kendisine ait yönetim işlemleri olacağından dolayı, bu eylemleri gerçekleştirebilecek token’da
adminyetkisinin bulunması gerekmektedir. Bunun için biraz önce oluşturduğumuzrestapi-playgroundclient’ının Service accounts roles sekmesi üzerinden Realm roles butonu aracılığıyla ilgili yetki verilirse eğer bu client’tan üretilecek token’lara bu yetki eklenmiş olacaktır.
- Realm Oluşturma
Bir realm oluşturmak için aşağıdaki endpoint’e POST isteğinde bulunmak gerekmektedir:🔗 POST /admin/realms
Body:
{ "realm": string, "enabled": boolean, "displayName": string }Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(realmRequest), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PostAsync("/admin/realms", content); - Realm’leri Listeleme
Erişilebilir realm’lerin bir listesini elde edebilmek için aşağıdaki endpoint’e GET isteğinde bulunmak gerekmektedir:🔗 GET /admin/realms
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.GetAsync("/admin/realms"); var data = await response.Content.ReadFromJsonAsync<dynamic>(); - Realm Silme
Bir realm’i silebilmek için aşağıdaki endpoint’e DELETE isteğinde bulunmak gerekmektedir:🔗 DELETE /delete-realm/{realm}
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.DeleteAsync($"/admin/realms/{realm}"); - Realm’in Temel Ayarlarını Güncelleme
Oluşturulmuş bir realm’in temel ayarlarını değiştirebilmek için aşağıdaki endpoint’e PUT isteğinde bulunmak gerekmektedir:🔗 PUT /admin/realms/{realm}
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); //Realm elde ediliyor var getRealmResponse = await httpClient.GetAsync($"/admin/realms/{realm}"); var realmToUpdate = await getRealmResponse.Content.ReadFromJsonAsync<dynamic>(); var content = new StringContent( JsonSerializer.Serialize(new { realm = "updated-example-realm", enabled = false, displayName = "Updated Example Realm", loginWithEmailAllowed = false, //Email ile login resetPasswordAllowed = true, //Şifre sıfırlama verifyEmail = true, //Email doğrulama accessTokenLifespan = 99999, //Access token ömrü bruteForceProtected = true, //Brute force koruması supportedLocales = new string[] { "tr", "en" } //Desteklenen diller }), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PutAsync($"/admin/realms/{realm}", content);Burada görüldüğü üzere önce oluşturulan realm elde edilmekte, ardından istenen özellikler(ki daha fazla detayda ayar alanı mevcuttur) JSON formatında gönderilerek gerekli güncellemeler gerçekleştirilmektedir.
- Realm Oluşturma
-
Client Yönetimi
Client yönetimi de Keycloak’ın yönetim işlemine gireceği için
adminyetkisi gerektirmektedir. Bunun için de client’ı hangi realm’de oluşturacaksak oluşturalım,adminyetkisi sade ve sadecemasterrealm’inde bulunacağından dolayırestapi-playgroundclient’ından bir access token ile oluşturmamız gerekecektir.- Client Oluşturma
Herhangi bir realm’e, bir client oluşturmak için aşağıdaki endpoint’e POST isteğinde bulunmak gerekmektedir:🔗 POST /admin/realms/{realm}/clients
Body:
{ "clientId": string, "enabled": boolean, "protocol": string, "publicClient": boolean, "serviceAccountsEnabled": boolean, "directAccessGrantsEnabled": boolean }Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(clientRequest), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PostAsync($"/admin/realms/{realm}/clients", content); - Client’ları Listeleme
İlgili realm içerisindeki tüm client’ları elde etmek için aşağıdaki endpoint’e GET isteğinde bulunmak gerekmektedir:🔗 GET /admin/realms/{realm}/clients
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.GetAsync($"/admin/realms/{realm}/clients"); var data = await response.Content.ReadFromJsonAsync<dynamic>(); - Client Silme
Herhangi bir client’ı silmek için aşağıdaki endpoint’e DELETE isteğinde bulunmak gerekmektedir:🔗 DELETE /admin/realms/{realm}/clients/{clientId}
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.DeleteAsync($"/admin/realms/{realm}/clients/{clientId}");Burada ‘clientId’ değerinin, ilgili client’ın Guid türünden id değeri olduğuna dikkatinizi çekerim.
- Client Güncelleme
Oluşturulmuş bir client’ın temel ayarlarını değiştirebilmek için aşağıdaki endpoint’e PUT isteğinde bulunmak gerekmektedir:🔗 PUT /admin/realms/{realm}/clients/{clientId}
Örnek C# kodu;
//Client ilgili realm üzerinden elde ediliyor var getClientResponse = await httpClient.GetAsync($"/admin/realms/{realm}/clients/{clientId}"); var clientToUpdate = await getClientResponse.Content.ReadFromJsonAsync<dynamic>(); var content = new StringContent( JsonSerializer.Serialize(new { id = clientId, clientId = "Updated Client", enabled = false, serviceAccountsEnabled = false, directAccessGrantsEnabled = true }), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PutAsync($"/admin/realms/{realm}/clients/{clientId}", content);
- Client Oluşturma
-
Kullanıcı Yönetimi
Kullanıcı yönetimi için
manage-usersveview-usersyetkisinin alınması yeterli olacaktır.- Kullanıcı Oluşturma
Bir kullanıcı oluşturmak için aşağıdaki endpoint’e POST isteğinde bulunmak gerekmektedir:🔗 POST /admin/realms/{realm}/users
Body:
{ "username": string, "enabled": boolean, "email": string, "emailVerified": boolean, "firstName": string, "lastName": string }Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(userRequest), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PostAsync($"/admin/realms/{realm}/users", content); - Kullanıcı Listeleme
Bir realm’deki tüm kullanıcıları listelemek için aşağıdaki endpoint’e GET isteğinde bulunulması gerekmektedir:🔗 GET /admin/realms/{realm}/users
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.GetAsync($"/admin/realms/{realm}/users"); var data = await response.Content.ReadFromJsonAsync<dynamic>(); - Kullanıcı Silme
Bir kullanıcıyı silmek için aşağıdaki endpoint’e DELETE isteğinde bulunmak gerekmektedir:🔗 DELETE /admin/realms/{realm}/users/{userId}
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.DeleteAsync($"/admin/realms/{realm}/users/{userId}"); - Kullanıcıyı Aktif / Pasif Yapma
Kullanıcının durumunu aktif ya da pasif yapabilmek için aşağıdaki endpoint’e uygun verilerle PUT isteğinde bulunmak gerekmektedir:🔗 PUT /admin/realms/{realm}/users/{userId}
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); //User çekiliyor. var userResponse = await httpClient.GetAsync($"/admin/realms/{realm}/users/{userId}"); var user = await userResponse.Content.ReadFromJsonAsync<dynamic>(); var content = new StringContent( JsonSerializer.Serialize(new { id = userId, username = user.GetProperty("username").GetString(), enabled = status }), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PutAsync($"/admin/realms/{realm}/users/{userId}", content); - Kullanıcının E-Posta Bilgisini Güncelleme
Benzer mantıkla kullanıcının e-posta bilgisini güncelleyebilmek için de aşağıdaki endpoint’e PUT isteğinde bulunulması gerekmektedir:🔗 PUT /admin/realms/{realm}/users/{userId}
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(new { id = userId, email = emailRequest.Email, emailVerified = emailRequest.EmailVerified }), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PutAsync($"/admin/realms/{realm}/users/{userId}", content); - Initial Password Atama
Initial password, kullanıcı oluşturulduktan sonra admin tarafından atanan ilk (geçici) şifredir. Haliyle bu şifreyi kullanıcıya atabilmek için aşağıdaki endpoint’e PUT isteğinde bulunulması gerekmektedir:🔗 PUT /admin/realms/{realm}/users/{userId}/reset-password
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(new { type = "password", value = temporaryPassword, temporary = true }), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PutAsync($"/admin/realms/{realm}/users/{userId}/reset-password", content);
- Kullanıcı Oluşturma
-
Role Yönetimi
Keycloak’ta rol oluşturmak ve yönetmek için
admin‘in dışında ihtiyacımız olan hususi yetkilermanage-realmveyamanage-clientsyetkileri eşliğindeview-realmyetkileridir.- Realm Role Oluşturma
Herhangi bir realm’e rol tanımlayabilmek için aşağıdaki endpoint’e POST isteğinde bulunmak gerekmektedir:🔗 POST /admin/realms/{realm}/roles
Body:
{ "name": string, "description": string }Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(roleRequest), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PostAsync($"/admin/realms/{realm}/roles", content); - Client Role Oluşturma
Aynı şekilde herhangi bir client’a rol tanımlayabilmek için ise aşağıdaki endpoint’e POST isteğinde bulunmak gerekmektedir:🔗 POST /admin/realms/{realm}/clients/{clientId}/roles
Body:
{ "name": string, "description": string }Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(roleRequest), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PostAsync($"/admin/realms/{realm}/clients/{clientId}/roles", content); - Rolleri Listeleme
Tüm rolleri listelemek için aşağıdaki endpoint’lere GET isteklerinde bulunmak gerekmektedir:🔗 GET /admin/realms/{realm}/roles
🔗 GET /admin/realms/{realm}/clients/{clientId}/roles
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.GetAsync($"/admin/realms/{realm}/roles"); var data = await response.Content.ReadFromJsonAsync<dynamic>(); - Role Silme
Bir rolü silmek için aşağıdaki endpoint’lere DELETE isteklerinde bulunmak gerekmektedir:🔗 DELETE /admin/realms/{realm}/roles/{role}
🔗 DELETE /admin/realms/{realm}/clients/{clientId}/roles/{role}
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.DeleteAsync($"/admin/realms/{realm}/roles/{role}"); - Role Güncelleme
Bir rolü güncellemek için aşağıdaki endpoint’lere PUT isteklerinde bulunmak gerekmektedir:🔗 PUT /admin/realms/{realm}/roles/{role}
🔗 PUT /admin/realms/{realm}/clients/{clientId}/roles/{role}
Body:
{ "name": string, "description": string }Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(roleRequest), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PutAsync($"/admin/realms/{realm}/roles/{role}", content); - Kullanıcıya Rol Atama
Rol atama işlemleri için token’da
manage-usersyetkisine ihtiyaç vardır.Bir kullanıcıya realm rolü atarken aşağıdaki endpoint’e POST isteğinde bulunmak gerekmektedir:
🔗 POST /admin/realms/{realm}/users/{userId}/role-mappings/realm
Body:
[ { "id": string, "name": string } ]Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(new List<RoleMappingRequest> { roleMappingRequest }), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PostAsync($"/admin/realms/{realm}/users/{userId}/role-mappings/realm", content);Benzer mantıkla, kullanıcıya client rolü atayabilmek için aşağıdaki endpoint kullanılmalıdır:
🔗 POST /admin/realms/{realm}/users/{userId}/role-mappings/clients/{clientId}/realm
- Realm Role Oluşturma
-
Scope Yönetimi
Scope işlemlerini yürütebilmek için
manage-realmyetkisi yeterli olacaktır.- Scope Oluşturma
🔗 POST /admin/realms/{realm}/roles
Body:
{ "name": string, "protocol": string, "description": string }Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var content = new StringContent( JsonSerializer.Serialize(scopeRequest), Encoding.UTF8, MediaTypeNames.Application.Json); var response = await httpClient.PostAsync($"/admin/realms/{realm}/client-scopes", content); - Scope’u Default / Optional Olarak Client’a Bağlama
Bir scope’u Default olarak client’a bağlayabilmek için aşağıdaki endpoint’e PUT isteğinde bulunmak gerekmektedir:🔗 PUT /admin/realms/{realm}/clients/{clientId}/default-client-scopes/{scopeId}
Örnek C# kodu;
httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, $"Bearer {token.AccessToken}"); var response = await httpClient.PutAsync($"/admin/realms/{realm}/clients/{clientId}/default-client-scopes/{scopeId}", null);Benzer mantıkla Optional olarak bağlayabilmek için ise aşağıdaki endpoint kullanılmalıdır:
🔗 POST /admin/realms/{realm}/clients/{clientId}/optional-client-scopes/{scopeId}
- Scope Oluşturma
-
Login İşlemleri – Token Alma
- Kullanıcı Login Olma
Keycloak’da kullanıcı için kullanıcı adı ve şifre ile login olup token alabilmek Direct access grants akışı gerektiren bir süreçtir. Dolayısıyla burada direktadmin-cli‘ı tercih edebilir ya da bu akış türünde bir client oluşturup kullanabilirsiniz.🔗 /realms/{realm}/protocol/openid-connect/token
Body:
{ "grant_type": string, "client_id": string, "username": string, "password": string, "scope": string }Örnek C# kodu;
var content = new FormUrlEncodedContent(new Dictionary<string, string> { ["grant_type"] = "password", ["client_id"] = "admin-cli", ["username"] = "admin", ["password"] = "admin", ["scope"] = "openid", }); var response = await httpClient.PostAsync($"/realms/{realm}/protocol/openid-connect/token", content); var token = await response.Content.ReadFromJsonAsync<dynamic>();
- Kullanıcı Login Olma
5 Kritik Soru / 5 Kritik Cevap
Şimdi de son olarak, konuya dair akıllara gelebilecek 5 olası kritik soruyu cevaplandırmaya çalışıp içeriğimizi nihayete erdirebiliriz.
Soru 1 | Admin API çağrıları neden backend’de olmalı?
Keycloak Admin REST API’si, uygulama fonksiyonelliği yerine kimlik altyapısı yönetimi sağladığı için kullanıcı oluşturma, silme, rol atama, client yapılandırma, token içeriğine müdahalede bulunma vs. gibi işlemleri yürütebilmemizi sağlamaktadır. Dolayısıyla bu işlemler bir endpoint çağrımından öte -kimlik sistemini yeniden şekillendirmektir-!
Bu seviyedeki işlemler illa ki rate limit gerektirecektir. Hatta audit log, erişim kontrolü ve gerektiğinde merkezi olarak kontrol gerektirecektir…
Bu API’lere çağrıları backend’den gerçekleştirerek en azından kim tarafından, ne zaman, hangi yetkiyle bu isteklerin yapıldığı kontrol edilebilecek ve böylece daha güvenli bir yaklaşım sergilenmiş olacaktır.
Soru 2 | Admin API’leri frontend’den çağrılmalı mı?
Frontend, güvenli bir ortam olmadığı için tabi ki de çağrılmamalıdır! Çünkü frontend demek; client secret’ın gizli kalmaması demek… token’ın kullanıcıya sızması demek… network üzerinden manipülasyona açık olmak demek… reverse engineering ihtimalini artırmak demek…
Keycloak Admin API çağrıları için gereken token; client_credentials akışını ve confidential client özelliğini gerektirmektedir. Ayrıca süreçte client secret değeri de içerecektir. Haliyle bu secret değerin frontend’e verilmesi demek -her selamün aleyküm diyene Keycloak’ın yönetimini verelim- demektir!
Soru 3 | Client secret nerede tutulmalıdır?
Client secret değeri sade ve sadece server-side’da tutulmalıdır. Asp.NET Core Secret Manager, Environment Variable yahut AWS Secrets Manager bu tarz verilerin tutulması için bizlere sunulan hazır platformlardır.
Soru 4 | Master realm neden gereksiz yere kullanılmamalıdır?
Master realm, Keycloak’ın root yetki alanıdır. Master realm’den alınan token; tüm realm’leri görebilmesi ve yönetebilmesinden kaynaklı yanlış kullanım durumlarında geri dönüşü olmayan hasarlara yol açabilir.
Eğer ki, sadece tek bir realm içerisindeki kullanıcılar, client’lar veya scope’lar yönetilecekse, bu durumda master realm’in tercih edilmesi, yetki alanını gereksiz yere büyütmek demek olacaktır.
Sonuç olarak;
İçerik boyunca, Keycloak’u REST API üzerinden nasıl yapılandırabileceğimizi incelemiş ve Keycloak’a pratiksel temaslarla deneyimlerde bulunmuş vaziyetteyiz. Artık burada edindiğimiz kazanımlarla, sonraki içeriklerimizde Keycloak ile ilgili çalışmaları daha da derinleştirecek ve yavaş yavaş bulunduğumuz fonksiyonel seviyelerden mimarisel boyutlara Keycloak’a hakimiyetimizi artırıyor olacağız.
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Not : Örnek çalışmayı aşağıdaki GitHub adresinden erişebilirsiniz.
https://github.com/gncyyldz/Keycloak.Examples/tree/master/Keycloak1.Client.Demo
