> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diyanet.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Servisi

> Google, Meta ve Keycloak OAuth2 entegrasyonu — BuildingBlocks.OAuth soyutlaması, PKCE (S256) ve backend-driven akış.

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>`

```csharp theme={null}
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`):

```csharp theme={null}
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)

<Steps>
  <Step title="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.
  </Step>

  <Step title="Code">
    Kullanıcı sağlayıcıda giriş yapar, callback URL'e `code` + `state` ile döner.
  </Step>

  <Step title="Exchange">
    `ExchangeCodeAsync(code, codeVerifier)` → token endpoint'ine `grant_type=authorization_code` ile POST atar, `OAuthTokenResponse` alır.
  </Step>

  <Step title="UserInfo">
    `GetUserInfoAsync(accessToken)` → userinfo endpoint'inden profil çeker.
  </Step>
</Steps>

## 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`):

```csharp theme={null}
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):

```csharp theme={null}
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:

```csharp theme={null}
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);
```

<Note>
  `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.
</Note>

`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.

```json theme={null}
{
  "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`

```csharp theme={null}
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)));
```

<CardGroup cols={2}>
  <Card title="Keycloak Kurulumu" icon="key" href="/keycloak/overview">
    Çift realm yapılandırması ve provisioning.
  </Card>

  <Card title="Auth Mimarisi" icon="lock" href="/security/overview">
    Login akışları, JWT ve RBAC entegrasyonu.
  </Card>
</CardGroup>
