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

# BuildingBlocks.OAuth

> OAuth2 + PKCE soyutlaması — Google, Meta ve Keycloak için provider-agnostik authorize/exchange/userinfo akışı.

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

<Info>
  Provider implementasyonları, DI kaydı (`AddOAuthProviders`) ve uçtan uca giriş akışı [Altyapı Servisleri / OAuth](/services/oauth) sayfasında anlatılır. Bu sayfa paketteki arayüz ve modellere odaklanır.
</Info>

## `IOAuthClient<TUserInfo>`

Tüm provider'lar bu generic kontratı uygular. `TUserInfo`, sağlayıcıya özgü kullanıcı profili tipidir (örn. `GoogleUserInfoDto`).

| Metot                             | İmza                                                                           | Amaç                                                                          |
| --------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| `CreateAuthorizationRequestAsync` | `Task<AuthorizationRequest> CreateAuthorizationRequestAsync(string state)`     | Authorize URL + PKCE challenge üretir; `state` client context/returnUrl taşır |
| `ExchangeCodeAsync`               | `Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string codeVerifier)` | Callback'teki `code`'u token'a çevirir (PKCE verifier ile)                    |
| `GetUserInfoAsync`                | `Task<TUserInfo> GetUserInfoAsync(string accessToken)`                         | Access token ile provider'dan kullanıcı profilini çeker                       |

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

<Note>
  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ı](#pkce-nin-redis-te-saklanmas%C4%B1).
</Note>

### Modeller

| Tip                    | Üyeler                                                                                                         | Açıklama                                                                         |
| ---------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `AuthorizationRequest` | `Url`, `CodeVerifier`, `CodeChallenge`, `State` (hepsi `init`)                                                 | Authorize akışını başlatmak için gereken bilgi paketi                            |
| `OAuthTokenResponse`   | `AccessToken`, `TokenType`, `ExpiresIn`, `RefreshToken`, `RefreshExpiresIn`, `Scope`, `IdToken`, `IdTokenType` | Token endpoint'inin `snake_case` JSON yanıtı (`[JsonPropertyName]` ile maplenir) |
| `OAuthUserInfo`        | `Sub`, `FirstName`, `LastName`, `DisplayName`, `Roles`, `Uid`                                                  | Provider-bağımsız temel kullanıcı bilgisi                                        |
| `OAuthContextType`     | `Identity`, `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).

| Üye             | Tip      | Üretim                                                      |
| --------------- | -------- | ----------------------------------------------------------- |
| `CodeVerifier`  | `string` | `RandomNumberGenerator.GetBytes(64)` → Base64URL            |
| `CodeChallenge` | `string` | `SHA256(verifier)` → Base64URL (`=` kırpılır, `+→-`, `/→_`) |

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

```csharp theme={null}
// 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                                                                                                                                                        |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OAuthOptions`         | `OAuthProviderOptions Google`, `OAuthProviderOptions Meta`                                                                                                    |
| `OAuthProviderOptions` | `OAuthFlowOptions Identity`, `OAuthFlowOptions Integration`                                                                                                   |
| `OAuthFlowOptions`     | `ClientId`, `ClientSecret`, `AuthorizationEndpoint`, `TokenEndpoint`, `RedirectUri`, `Issuer`, `OpenIDConnectDiscoveryEndpoint`, `UserInfoEndpoint`, `Scopes` |

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

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

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

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

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

## İlgili

<CardGroup cols={2}>
  <Card title="OAuth servisleri" icon="plug" href="/services/oauth">
    Google/Meta/Keycloak istemcileri, AddOAuthProviders ve uçtan uca akış.
  </Card>

  <Card title="Keycloak" icon="key" href="/building-blocks/keycloak">
    Çift realm JWT doğrulama ve claims transformation.
  </Card>

  <Card title="JWT" icon="id-card" href="/building-blocks/jwt">
    Exchange sonrası uygulama token'ının üretimi.
  </Card>

  <Card title="Cache" icon="layer-group" href="/building-blocks/caching">
    PKCE verifier'ının Redis'te saklanması.
  </Card>
</CardGroup>
