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

# Sorun Giderme

> Realm metadata UNREACHABLE, issuer mismatch, invalid audience, PKCE/redirect, token süresi ve provisioning logları

Keycloak entegrasyonunda hataların çoğu URL/issuer/audience uyuşmazlığından kaynaklanır. Bu
sayfa en sık görülenleri ve teşhis yollarını toplar.

<Tip>
  İlk bakacağınız yer: API console. `BuildingBlocks.Keycloak/DependencyInjection.cs` açılışta her
  realm için `Authority`, `PublicIssuer` ve metadata erişim durumunu (`REACHABLE` / `UNREACHABLE`)
  yazar. Ayrıca her başarısız doğrulama `X-Auth-Fail-{Scheme}` response header'ına yazılır.
</Tip>

<AccordionGroup>
  <Accordion title="Realm metadata UNREACHABLE (BaseUrl vs PublicBaseUrl)">
    Console'da şuna benzer satır:

    ```text theme={null}
    [Keycloak JWT] Personel metadata UNREACHABLE ✗ (http://diyanet-keycloak:8080/realms/.../.well-known/openid-configuration) → HttpRequestException: ...
    ```

    API, `BaseUrl`'e ulaşamıyor demektir. JWKS alınamazsa tüm token doğrulama `IDX10500` ile patlar.

    **Çözüm:**

    * Docker dev'de API container'ı Keycloak'a **iç ağ hostname'i** ile ulaşmalı (`BaseUrl =
      http://diyanet-keycloak:8080`), `localhost` değil — container içinde `localhost` API'nin
      kendisidir.
    * Aynı anda tarayıcının gördüğü URL `PublicBaseUrl = http://localhost:8080` olmalı.
    * Test: `docker exec diyanet-api curl -s http://diyanet-keycloak:8080/realms/diyanet-yonetim-dev-realm/.well-known/openid-configuration`
  </Accordion>

  <Accordion title="Issuer mismatch (internal vs public issuer)">
    Token reddediliyor; `X-Auth-Fail-...` header'ında issuer hatası. Sebep: token'ın `iss`'i
    (`PublicBaseUrl`) ile API'nin beklediği issuer (`BaseUrl`) farklı.

    Sistem her ikisini de kabul edecek şekilde tasarlandı:

    ```csharp theme={null}
    var validIssuers = scheme.Issuer == scheme.PublicIssuer
        ? new[] { scheme.Issuer }
        : new[] { scheme.Issuer, scheme.PublicIssuer };
    ```

    **Çözüm:** `PublicBaseUrl`'ü token'ın gerçek `iss`'iyle birebir aynı yapın. `iss`'i jwt.io ile
    okuyun; `appsettings`'teki `PublicBaseUrl` + `/realms/{Realm}` bununla eşleşmeli. Sondaki `/`
    farkı bile mismatch yaratır.
  </Accordion>

  <Accordion title="Invalid audience">
    `SecurityTokenInvalidAudienceException`. Token'ın `aud`'u kabul edilen değerlerle eşleşmiyor.

    Kabul edilenler: client ID **ve** Keycloak'ın `account` audience'ı:

    ```csharp theme={null}
    ValidAudiences = [scheme.ClientId, "account"];
    ```

    **Çözüm:**

    * `appsettings` → `Keycloak:*:ClientId` ile token'daki `aud` aynı olmalı (`diyanet-website` /
      `diyanet-admin`).
    * Geçici teşhis için `ValidateAudience: false` yapılabilir ama prod'da açık tutun.
  </Accordion>

  <Accordion title="PKCE / redirect URI hatası">
    Keycloak login sonrası `Invalid parameter: redirect_uri` veya PKCE hatası.

    **Çözüm:**

    * Client'ın **Valid redirect URIs** listesi SPA origin'ini içermeli: `http://localhost:3000/*`
      (website), `http://localhost:3001/*` (admin) ve Swagger için
      `http://localhost:5005/swagger/oauth2-redirect.html`.
    * **Web origins** SPA origin'ini içermeli (CORS).
    * Client public + `PkceRequired: true` + method `S256` olmalı; SPA `keycloak-js` ile PKCE
      gönderiyor olmalı.
  </Accordion>

  <Accordion title="Token süresi — personel 5 dakikada 401">
    Personel token'ı bilinçli olarak 5 dakikadır (`AccessTokenLifespanSeconds: 300`). Süre dolunca
    401 ve `Token-Expired: true` header'ı gelir.

    **Çözüm:** Bu beklenen davranıştır. SPA refresh token rotation ile (lifespan 1800 sn) sessizce
    yeniler. 5 dakikada bir login'e düşüyorsanız refresh akışınızı kontrol edin; refresh token süresi
    de dolmuşsa yeniden login gerekir. Saat kayması için 30 sn `ClockSkew` toleransı vardır.
  </Accordion>

  <Accordion title="Her endpoint 403 — account_status / MapInboundClaims">
    Authenticated görünüyorsunuz ama her şey 403.

    **Olası sebep:** `MapInboundClaims` açık kalmış → `sub` claim'i yeniden adlandırılmış →
    `UserContextClaimsTransformation` `sub`'u bulamaz → `account_status="Unknown"` →
    `ActiveAccountRequirement` fail.

    **Çözüm:** `ConfigureScheme`'te `options.MapInboundClaims = false` olduğundan emin olun. Ayrıca
    kullanıcının DB'deki durumu `Active` olmalı; yeni kayıt (Pending) ise endpoint
    `[AllowPendingAccount]` taşımıyorsa 403 normaldir.
  </Accordion>

  <Accordion title="Provisioning loglarını izleme">
    Provisioning `[Provisioning]` öneki ile loglar:

    ```text theme={null}
    [Provisioning] Keycloak hazır bekleniyor (max 120s) → http://localhost:8080/realms/master/.well-known/openid-configuration
    [Provisioning] ✅ Keycloak hazır.
    [Provisioning] Başlatılıyor — mod: RunOnce
    [Provisioning] ✅ Tamamlandı.
    ```

    `Keycloak'a ulaşılamıyor` tekrarlıyorsa `KeycloakProvisioning:Admin:BaseUrl` yanlış ya da
    Keycloak henüz ayağa kalkmadı (120 sn'ye kadar bekler). Hata olsa bile uygulama yine başlar;
    o yüzden realm yokmuş gibi davranan 401'lerde önce bu logları kontrol edin.

    ```bash theme={null}
    docker logs diyanet-api -f | Select-String "Provisioning|Keycloak JWT"
    ```
  </Accordion>

  <Accordion title="Kapalı ağ (intranet) senaryosu">
    Müşteri ortamı kurumsal TLS sertifikalı kapalı bir intranet olabilir (Let's Encrypt çalışmaz).

    **Notlar:**

    * `KEYCLOAK_BASE_URL` / `KEYCLOAK_EXTERNAL_URL` iç DNS adını işaret etmeli.
    * API, kurumsal kök sertifikaya güvenmeli — gerekirse `scripts/export-ca-cert.ps1` ile CA
      sertifikası container trust store'una eklenir.
    * `RequireHttpsMetadata: true` tutulur; metadata HTTPS üzerinden alınır.
    * Provisioning kapalı, realm bir kez elle/tek-sefer kurulur.
  </Accordion>
</AccordionGroup>

## Hızlı kontrol komutları

```bash theme={null}
# OIDC discovery (issuer / jwks_uri görün)
curl -s http://localhost:8080/realms/diyanet-yonetim-dev-realm/.well-known/openid-configuration | jq '{issuer, jwks_uri}'

# API container'dan Keycloak'a erişim (BaseUrl testi)
docker exec diyanet-api curl -s -o /dev/null -w "%{http_code}\n" \
  http://diyanet-keycloak:8080/realms/diyanet-vatandas-dev-realm/.well-known/openid-configuration

# Başarısız auth teşhis header'ını gör
curl -v http://localhost:5005/api/admin/users -H "Authorization: Bearer <token>" 2>&1 | grep -i "x-auth-fail\|token-expired"
```

## İlgili

<CardGroup cols={2}>
  <Card title="Ortamlar" href="/keycloak/environments">
    BaseUrl vs PublicBaseUrl ayrımı.
  </Card>

  <Card title="Authentication" href="/security/authentication">
    Issuer / audience / MapInboundClaims.
  </Card>

  <Card title="Provisioning" href="/keycloak/provisioning">
    Hosted service ve loglar.
  </Card>

  <Card title="Client Yapılandırması" href="/keycloak/client-configuration">
    Redirect URI / PKCE ayarları.
  </Card>
</CardGroup>
