Skip to main content
Personel (yönetim paneli) girişi vatandaş akışından farklıdır: OTP/SMS yoktur, kimlik doğrulama Keycloak üzerinden OIDC ile yapılır. API token’ı kendisi üretmez; Keycloak’ın imzaladığı access token’ı JWKS ile doğrular. İlk girişte kullanıcı davet bazlı olarak lokal DB’ye eşlenir — daveti olmayan kişi 403 not_invited alır.
İlgili dosyalar: src/DiyanetCleanArchitecture.API/Controllers/AuthController.cs (Keycloak akışları) · Application/Features/Authentication/Admin/Commands/ProvisionKeycloakUser/* · API/SeedWork/Authorization/ActiveAccountAuthorizationHandler.cs · Application/Features/Users/Admin/Queries/GetAdminMe/*

Realm ve client

Realm (dev)ClientToken süresi
Personel (yönetim)diyanet-yonetim-dev-realmdiyanet-admin5 dk
Vatandaş (website)diyanet-vatandas-dev-realmdiyanet-website2 saat
Admin SPA, keycloak-js ile Authorization Code + PKCE (S256) akışını kullanır. 5 dakikalık kısa access token, keycloak-js’in sessiz (silent) refresh mekanizmasıyla yenilenir.

Sequence — ilk personel girişi

Adım adım

1

SPA → Keycloak (PKCE)

Admin SPA keycloak-js ile Authorization Code + PKCE akışını başlatır. Kullanıcı Keycloak login formunda kimlik doğrular; SPA access_token (5 dk) ve refresh_token alır. API bu adımda devrede değildir.
2

Session — token doğrulama + cookie

SPA token’ı keycloak/session endpoint’ine gönderir. API token’ı JWKS ile offline doğrular (Keycloak’a gidilmez) ve HTTP-only cookie set eder:
[AllowAnonymous]
[HttpPost("keycloak/session")]
public async Task<IActionResult> KeycloakSession([FromBody] KeycloakSessionRequest request, CancellationToken ct)
{
    var payload = await _keycloakValidator.ValidateAsync(request.AccessToken, ct); // JWKS
    Response.AppendKcToken(request.AccessToken, accessExpiry, SecureCookie);
    if (!string.IsNullOrEmpty(request.RefreshToken))
        Response.AppendRefreshToken(request.RefreshToken, refreshExpiry, SecureCookie);
    // → provisioning (sonraki adım)
}
Raw token cookie’ye gömülür; React bir daha token’ı header’a koymaz, tüm istekler kc_token cookie’siyle gider (BFF deseni). SecureCookie dev’de false, prod’da true’dur.
3

Provisioning — davet bazlı eşleme

Doğrulanan payload ProvisionKeycloakUserCommand’a dönüştürülür. Handler yalnızca Personel realm için çalışır (ResolveRealmKind(iss)); başka realm gelirse unknown_realm ile reddeder. Sonra üç olasılık denenir, hepsi tek transaction içinde:
// 1) sub ile mevcut Keycloak linki var mı?
var bySub = await _userRepository.FirstOrDefaultAsync(
    new UserByIdentityProviderSpecification(AuthProviderType.Keycloak, request.Subject), ct);
if (bySub is not null) {
    if (bySub.IsBlocked) throw new ExternalLoginNotAllowedException("...", "account_blocked");
    bySub.AddOrUpdateKeycloakLogin(request.Subject, displayName);
    wasFirstLogin = bySub.ActivateOnExternalLogin();   // Draft/Pending → Active
    // ... save + commit
}
// 2) email ile önceden davet edilmiş (pre-created) kullanıcı?
//    byEmail.AddOrUpdateKeycloakLogin + ActivateOnExternalLogin
// 3) hiçbir eşleşme yok → reddet
throw new ExternalLoginNotAllowedException(
    "Yönetim paneli erişim yetkiniz bulunmuyor. ...", "not_invited");
User.ActivateOnExternalLogin() idempotenttir: Draft/Pending ise Active’e geçirip true döner, zaten Active ise false. Banned/Suspended kullanıcılara dokunmaz; login zaten reddedilir.
4

403 senaryoları — cookie temizliği

Provisioning bir ExternalLoginNotAllowedException (not_invited / account_blocked / unknown_realm) ya da beklenmeyen hata fırlatırsa controller cookie’leri temizler ve 403 döner — daveti olmayan kullanıcının dashboard’a düşmesi engellenir:
catch (ExternalLoginNotAllowedException ex)
{
    Response.DeleteKcToken();
    Response.DeleteRefreshToken();
    return StatusCode(StatusCodes.Status403Forbidden, new { message = ex.Message, reason = ex.Reason });
}
Frontend bu reason koduna göre AccessDeniedScreen gösterir.
Concurrency notu: SPA aynı login’de iki paralel provision tetikleyebilir (keycloak/session’ın mediator.Send’i + JwtBearer.OnTokenValidated hook’u). İkisi de aynı (provider_type_id, provider_user_id) için INSERT dener; biri unique constraint (Postgres 23505) alır. Handler bu durumu idempotent başarı sayar ve false döner — UI 403 görmez.
5

Yetkili istekler — JwtBearer Personel şeması

Bundan sonraki tüm istekler kc_token cookie’siyle gelir. API’de JwtBearer “Personel” şeması token’ı doğrular ve claim’leri okur: permissions (multivalued), organization_id / tenant_id, accountStatus. Otorizasyon kararı her zaman lokal DB’deki rol + tenant’a dayanır; Keycloak’ın ClientRoles’u yetkiye dönüşmez (yalnızca audit/debug için loglanır).
6

Authorization — aktif hesap + permission policy

İki kademe çalışır. Önce ActiveAccountAuthorizationHandler hesabın accountStatus’unun aktif olduğunu kontrol eder; sonra endpoint’in [Authorize(Policy = "...")] permission policy’si permissions claim’iyle eşleştirilir. Pipeline’da Application tarafında ayrıca PermissionPipelineBehavior çalışır. Bkz. Authorization.
7

Profil — GetAdminMeQuery

SPA oturum sonrası kullanıcı profilini çeker:
GET /api/admin/me  →  GetAdminMeQuery  →  GetAdminMeQueryHandler
Handler oturum açan kullanıcının kimliğini, rollerini ve izinlerini döner; SPA menüyü/yetkileri buna göre çizer.

Token yenileme ve çıkış

  • Refresh: 5 dakikalık kısa token nedeniyle keycloak/refresh sık çağrılır. API refresh token’la Keycloak’tan yeni token alır, cookie’leri günceller ve IUserContextProvider.InvalidateAsync(sub) ile permission cache’ini düşürür (yetkiler değişmiş olabilir).
  • Logout: POST /api/auth/logout cookie’leri siler. Keycloak oturumunu da bitirmek için frontend ayrıca Keycloak end_session URL’ini açmalıdır.

Hata senaryoları

DurumreasonHTTP
Davet yok (eşleşme bulunamadı)not_invited403
Banned / Suspended hesapaccount_blocked403
Bilinmeyen / yanlış realmunknown_realm403
Provision sırasında beklenmeyen hataprovision_error403 (güvenli varsayılan: erişim reddedilir)
Geçersiz Keycloak token401

İlgili

Keycloak Provisioning

Realm/client/user otomatik kurulumu (RunOnce).

Authorization

Permission policy ve aktif hesap kontrolü.

Keycloak Ortamları

Dev/stage/prod realm farkları.

OTP / TOTP Akışı

Vatandaş tarafı yerel kimlik akışı.