Skip to main content
BuildingBlocks.OAuth paketi, harici kimlik sağlayıcılarla (Google, Meta, Keycloak) authorization-code + PKCE akışını tek bir provider-agnostik kontrat üzerinden yürütmek için gereken soyutlama ve modelleri barındırır. Somut HTTP istemcileri (KeycloakOAuthClient, GoogleIdentityOAuthClient, MetaIntegrationOAuthClient vb.) DiyanetCleanArchitecture.Infrastructure.Services.OAuth projesinde bu kontratı implement eder.
Provider implementasyonları, DI kaydı (AddOAuthProviders) ve uçtan uca giriş akışı Altyapı Servisleri / OAuth sayfasında anlatılır. Bu sayfa paketteki arayüz ve modellere odaklanır.

IOAuthClient<TUserInfo>

Tüm provider’lar bu generic kontratı uygular. TUserInfo, sağlayıcıya özgü kullanıcı profili tipidir (örn. GoogleUserInfoDto).
MetotİmzaAmaç
CreateAuthorizationRequestAsyncTask<AuthorizationRequest> CreateAuthorizationRequestAsync(string state)Authorize URL + PKCE challenge üretir; state client context/returnUrl taşır
ExchangeCodeAsyncTask<OAuthTokenResponse> ExchangeCodeAsync(string code, string codeVerifier)Callback’teki code’u token’a çevirir (PKCE verifier ile)
GetUserInfoAsyncTask<TUserInfo> GetUserInfoAsync(string accessToken)Access token ile provider’dan kullanıcı profilini çeker
namespace BuildingBlocks.OAuth.Abstractions;

public interface IOAuthClient<TUserInfo>
{
    Task<AuthorizationRequest> CreateAuthorizationRequestAsync(string state);
    Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string codeVerifier);
    Task<TUserInfo> GetUserInfoAsync(string accessToken);
}
Keycloak istemcisi (KeycloakOAuthClient) PKCE verifier’ı Redis’te tuttuğu için imzası biraz farklıdır — ExchangeCodeAsync(code, state) alır ve verifier’ı cache’ten kendisi çözer. Bkz. PKCE’nin saklanması.

Modeller

TipÜyelerAçıklama
AuthorizationRequestUrl, CodeVerifier, CodeChallenge, State (hepsi init)Authorize akışını başlatmak için gereken bilgi paketi
OAuthTokenResponseAccessToken, TokenType, ExpiresIn, RefreshToken, RefreshExpiresIn, Scope, IdToken, IdTokenTypeToken endpoint’inin snake_case JSON yanıtı ([JsonPropertyName] ile maplenir)
OAuthUserInfoSub, FirstName, LastName, DisplayName, Roles, UidProvider-bağımsız temel kullanıcı bilgisi
OAuthContextTypeIdentity, Integration (enum)Akış tipi ayrımı — aşağıya bakın

PKCE — PkcePair

PkcePair ctor’unda otomatik üretilir. code_verifier 64 byte rastgele veridir; code_challenge ise verifier’ın SHA-256 özetinin Base64URL kodudur (S256 metodu).
ÜyeTipÜretim
CodeVerifierstringRandomNumberGenerator.GetBytes(64) → Base64URL
CodeChallengestringSHA256(verifier) → Base64URL (= kırpılır, +→-, /→_)
var pkce = new PkcePair();
// authorize URL'ine: code_challenge={pkce.CodeChallenge}&code_challenge_method=S256
// token exchange'e:   code_verifier={pkce.CodeVerifier}

PKCE’nin Redis’te saklanması

code_verifier, authorize ile callback istekleri arasında saklanmalıdır (iki ayrı HTTP isteği). Keycloak istemcisi bunu IDistributedCache (Redis) üzerinde state key’iyle tutar:
  • Anahtar: keycloak_pkce_{state}
  • TTL: 10 dakika (StateExpiry)
  • Callback’te alınır ve tek kullanımlık olarak hemen silinir (RemoveAsync)
// CreateAuthorizationRequestAsync içinde
await _cache.SetStringAsync(
    "keycloak_pkce_" + state,
    pkce.CodeVerifier,
    new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) },
    ct);

// ExchangeCodeAsync içinde
var codeVerifier = await _cache.GetStringAsync("keycloak_pkce_" + state, ct)
    ?? throw new InvalidOperationException("PKCE code verifier bulunamadı. State geçersiz/süresi dolmuş.");
await _cache.RemoveAsync("keycloak_pkce_" + state, ct); // tek kullanım

Konfigürasyon — OAuthOptions

OAuth bölümüne bind edilir. Google ve Meta için iki ayrı flow (Identity ve Integration) tanımlanır; her flow kendi client kimliği ve endpoint’lerine sahiptir.
TipÜyeler
OAuthOptionsOAuthProviderOptions Google, OAuthProviderOptions Meta
OAuthProviderOptionsOAuthFlowOptions Identity, OAuthFlowOptions Integration
OAuthFlowOptionsClientId, ClientSecret, AuthorizationEndpoint, TokenEndpoint, RedirectUri, Issuer, OpenIDConnectDiscoveryEndpoint, UserInfoEndpoint, Scopes
Keycloak OAuthOptions’tan çıkarılmıştır. Keycloak tek config bloğu olarak Keycloak section’ından okunur (BuildingBlocks.Keycloak.KeycloakOptions veya OAuth projesindeki KeycloakOptions). Endpoint URL’leri BaseUrl + Realm’dan otomatik hesaplanır ({BaseUrl}/realms/{Realm}/protocol/openid-connect/auth vb.), config’e yazılmaz.
{
  "OAuth": {
    "Google": {
      "Identity": {
        "ClientId": "...apps.googleusercontent.com",
        "ClientSecret": "...",
        "AuthorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
        "TokenEndpoint": "https://oauth2.googleapis.com/token",
        "UserInfoEndpoint": "https://openidconnect.googleapis.com/v1/userinfo",
        "RedirectUri": "https://app.example/api/website/public/oauth/google/callback",
        "Scopes": ["openid", "profile", "email"]
      },
      "Integration": { "ClientId": "...", "ClientSecret": "...", "...": "..." }
    },
    "Meta": { "Identity": { "...": "..." }, "Integration": { "...": "..." } }
  },
  "Keycloak": {
    "BaseUrl": "http://keycloak:8080",
    "Realm": "diyanet",
    "ClientId": "diyanet-admin",
    "ClientSecret": "...",
    "RedirectUri": "https://app.example/api/admin/oauth/keycloak/callback",
    "Scopes": ["openid", "profile", "email"]
  }
}

Identity vs Integration

OAuthContextType iki kullanım senaryosunu ayırır:
  • Identity — kullanıcı bu provider ile giriş yapar (login/connect). Token uygulamaya kimlik kanıtlar.
  • Integration — kullanıcı zaten girişlidir; uygulama provider API’sine erişim için ek yetki alır (ör. Meta sayfa yönetimi). Farklı client/secret ve scope kullanır.

Kullanım — authorize → callback → exchange → userinfo

// 1) Authorize — kullanıcıyı provider'a yönlendir
[HttpGet("api/website/public/oauth/google/authorize")]
public async Task<IActionResult> Start([FromQuery] string returnUrl)
{
    var state = _stateProtector.Encode(returnUrl);     // context/returnUrl state'e gömülür
    var request = await _googleClient.CreateAuthorizationRequestAsync(state);

    // (Google istemcisinde) PKCE verifier'ı state ile cache'le
    await _cache.SetStringAsync($"pkce:{state}", request.CodeVerifier,
        new() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });

    return Redirect(request.Url);
}

// 2) Callback — code'u token'a çevir, profili çek
[HttpGet("api/website/public/oauth/google/callback")]
public async Task<IActionResult> Callback([FromQuery] string code, [FromQuery] string state)
{
    var codeVerifier = await _cache.GetStringAsync($"pkce:{state}");
    if (codeVerifier is null) return BadRequest("State geçersiz veya süresi dolmuş.");

    OAuthTokenResponse token = await _googleClient.ExchangeCodeAsync(code, codeVerifier);
    GoogleUserInfoDto userInfo = await _googleClient.GetUserInfoAsync(token.AccessToken);

    // Domain: GetOrCreateFromGoogleAsync → JWT issue
    var user = await _userFactory.GetOrCreateFromGoogleAsync(userInfo, ct);
    return Ok(new { token = _jwt.Issue(user) });
}
Keycloak istemcisinde adım 1/2 daha sadedir; verifier cache’i provider’ın kendi içindedir:
var req = await _keycloakClient.CreateAuthorizationRequestAsync(state, ct);  // verifier Redis'e yazılır
return Redirect(req.Url);
// ...callback...
var token = await _keycloakClient.ExchangeCodeAsync(code, state, ct);        // verifier Redis'ten okunur+silinir
state parametresini her zaman sunucu tarafında imzalı/şifreli üretin ve callback’te doğrulayın — CSRF ve open-redirect saldırılarına karşı ilk savunma hattıdır.

İlgili

OAuth servisleri

Google/Meta/Keycloak istemcileri, AddOAuthProviders ve uçtan uca akış.

Keycloak

Çift realm JWT doğrulama ve claims transformation.

JWT

Exchange sonrası uygulama token’ının üretimi.

Cache

PKCE verifier’ının Redis’te saklanması.