Skip to main content
OAuth servisi src/DiyanetCleanArchitecture.Infrastructure.Services.OAuth projesindedir ve üç sağlayıcıyı destekler: Google, Meta ve Keycloak. Ortak soyutlama BuildingBlocks.OAuth paketinde tanımlıdır.

Soyutlama — IOAuthClient<TUserInfo>

public interface IOAuthClient<TUserInfo>
{
    // PKCE + state dahil authorize URL üretir
    Task<AuthorizationRequest> CreateAuthorizationRequestAsync(string state);

    // authorization code → token exchange
    Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string codeVerifier);

    // access token ile provider'dan kullanıcı bilgisi çeker
    Task<TUserInfo> GetUserInfoAsync(string accessToken);
}
Her sağlayıcı için iki istemci vardır:
  • Identity istemcisi — kullanıcı kimliği (login): IGoogleIdentityOAuthClient, IMetaIdentityOAuthClient.
  • Integration istemcisi — geniş kapsamlı entegrasyon izinleri (örn. Google Business, Meta sayfaları/Instagram/WhatsApp): IGoogleIntegrationOAuthClient, IMetaIntegrationOAuthClient.

PKCE — PkcePair (S256)

Tüm akışlar PKCE kullanır. PkcePair 64 byte rastgele code_verifier üretir ve SHA-256 ile code_challenge türetir (code_challenge_method=S256):
public class PkcePair
{
    public string CodeVerifier { get; }
    public string CodeChallenge { get; }

    public PkcePair()
    {
        CodeVerifier = Base64UrlEncode(RandomNumberGenerator.GetBytes(64));
        CodeChallenge = Base64UrlEncode(SHA256.HashData(Encoding.ASCII.GetBytes(CodeVerifier)));
    }
}

Akış (backend-driven)

1

Authorize

CreateAuthorizationRequestAsync(state) → PKCE üretir, sağlayıcının authorize URL’ini (client_id, redirect_uri, response_type=code, scope, code_challenge, state) kurar.
2

Code

Kullanıcı sağlayıcıda giriş yapar, callback URL’e code + state ile döner.
3

Exchange

ExchangeCodeAsync(code, codeVerifier) → token endpoint’ine grant_type=authorization_code ile POST atar, OAuthTokenResponse alır.
4

UserInfo

GetUserInfoAsync(accessToken) → userinfo endpoint’inden profil çeker.

Google

GoogleIdentityOAuthClient, authorize/exchange/userinfo akışını implemente eder. Ayrıca id_token doğrulayan ayrı bir bileşen vardır — GoogleIdTokenVerifier (Google.Apis.Auth):
public async Task<GoogleTokenPayload> VerifyAsync(string idToken)
{
    var settings = new GoogleJsonWebSignature.ValidationSettings
    {
        Audience = new[] { _options.Google.Identity.ClientId }
    };

    var payload = await GoogleJsonWebSignature.ValidateAsync(idToken, settings);
    // ... Subject, Email, EmailVerified, Name, GivenName, FamilyName, Picture, Locale
}

Meta

MetaIdentityOAuthClient ve MetaIntegrationOAuthClient, Facebook v19.0 OAuth dialog ve Graph API endpoint’lerini kullanır. Integration scope’ları sayfa yönetimi, Instagram ve WhatsApp Business izinlerini içerir.

Keycloak

Keycloak istemcisi (KeycloakOAuthClient) diğerlerinden farklı olarak code_verifier’ı Redis’te saklar (IDistributedCache). Anahtar keycloak_pkce_{state}, TTL 10 dakika, tek kullanımlık (exchange’de okunup silinir):
private const string CacheKeyPrefix = "keycloak_pkce_";
private static readonly TimeSpan StateExpiry = TimeSpan.FromMinutes(10);

// CreateAuthorizationRequestAsync içinde:
await _cache.SetStringAsync(CacheKeyPrefix + state, pkce.CodeVerifier,
    new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = StateExpiry }, ct);

// ExchangeCodeAsync içinde:
var codeVerifier = await _cache.GetStringAsync(CacheKeyPrefix + state, ct);
await _cache.RemoveAsync(CacheKeyPrefix + state, ct);   // tek kullanım

Token doğrulama — KeycloakTokenValidator

Çift realm (Vatandaş + Personel) desteklenir. Validator, token’ı önce imza doğrulamadan okuyup iss claim’inden realm’i tespit eder, sonra ilgili realm’in JWKS endpoint’inden public key çekerek imzayı doğrular. JWKS 60 dakika IMemoryCache’te tutulur:
private static readonly TimeSpan JwksCacheDuration = TimeSpan.FromMinutes(60);

// Token okunur → iss'ten realm bulunur → JWKS alınır → ValidateToken
var issuer = unverified.Issuer;
var scheme = ResolveScheme(issuer);              // Personel / Vatandas
var signingKeys = await GetSigningKeysAsync(scheme, forceRefresh: false, ct);
JwtSecurityTokenHandler MapInboundClaims = false ile kurulur — böylece claim’ler sub, email, name gibi orijinal OIDC isimleriyle gelir. Aksi halde Microsoft.IdentityModel bunları uzun ClaimTypes.* URL’lerine çevirir ve sub/email lookup’ları boş döner.
SecurityTokenSignatureKeyNotFoundException alınırsa JWKS bir kez forceRefresh: true ile yeniden çekilir (key rotation senaryosu).

Config — OAuth ve Keycloak

Google/Meta OAuth kök bölümünden, Keycloak ise Keycloak kök bölümünden bind edilir.
{
  "OAuth": {
    "Google": {
      "Identity": {
        "ClientId": "...apps.googleusercontent.com",
        "ClientSecret": "...",
        "AuthorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
        "TokenEndpoint": "https://oauth2.googleapis.com/token",
        "RedirectUri": "https://api.example/auth/oauth/google/callback",
        "UserInfoEndpoint": "https://openidconnect.googleapis.com/v1/userinfo",
        "Scopes": [ "openid", "profile", "email" ]
      },
      "Integration": { "...": "geniş scope'lar (business.manage vb.)" }
    },
    "Meta": {
      "Identity":    { "...": "facebook.com/v19.0/dialog/oauth" },
      "Integration": { "...": "pages_*, instagram_*, whatsapp_*" }
    }
  },
  "Keycloak": {
    "Vatandas": { "Realm": "diyanet-vatandas-dev-realm", "ClientId": "diyanet-website", "...": "" },
    "Personel": { "Realm": "diyanet-yonetim-dev-realm", "ClientId": "diyanet-admin", "...": "" }
  }
}

DI kaydı — AddOAuthProviders

public static IServiceCollection AddOAuthProviders(this IServiceCollection services, IConfiguration configuration)
{
    services.Configure<OAuthOptions>(o => configuration.GetSection("OAuth").Bind(o));

    // Google — HttpClient + Polly retry
    services.AddHttpClient<IGoogleIdentityOAuthClient, GoogleIdentityOAuthClient>().AddPolicyHandler(GetRetryPolicy());
    services.AddHttpClient<IGoogleIntegrationOAuthClient, GoogleIntegrationOAuthClient>().AddPolicyHandler(GetRetryPolicy());
    services.AddSingleton<IGoogleIdTokenVerifier, GoogleIdTokenVerifier>();

    // Keycloak
    services.Configure<KeycloakOptions>(configuration.GetSection("Keycloak"));
    services.AddMemoryCache();   // JWKS cache
    services.AddHttpClient<IKeycloakOAuthClient, KeycloakOAuthClient>().AddPolicyHandler(GetRetryPolicy());
    services.AddSingleton<IKeycloakTokenValidator, KeycloakTokenValidator>();

    return services;
}

// 3 retry, exponential backoff (2^attempt sn)
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy() =>
    HttpPolicyExtensions.HandleTransientHttpError()
        .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));

Keycloak Kurulumu

Çift realm yapılandırması ve provisioning.

Auth Mimarisi

Login akışları, JWT ve RBAC entegrasyonu.