
# ASP.NET Core uygulamasına giriş ekleyin

Bu hızlı başlangıç, ASP.NET Core 10 Razor Pages uygulamasına `Microsoft.AspNetCore.Authentication.OpenIdConnect` ile KeenAuth girişi ekler. Bu paket, ASP.NET Core'un standart OpenID Connect istemcisidir. KeenAuth'un henüz bir .NET SDK'sı yoktur ve buna ihtiyaç da yoktur: KeenAuth standart bir OpenID Connect sağlayıcısıdır. Uygulamanız OAuth istemcisidir ve tüm token'ları sunucuda tutar; tarayıcıya yalnızca rastgele bir oturum anahtarı taşıyan bir çerez gider. Girişi yapılandırır, bir sayfayı korur, kullanıcının erişim token'ı ile korumalı bir API'yi çağırır, çıkış yapar ve uygulamayı çevrimdışı test edersiniz.

Uygulamanın rotaları şunlardır:

| Rota | Ne yapar |
|---|---|
| `GET /` | Herkese açık ana sayfa: bir giriş bağlantısı ya da kullanıcının adı ve bir çıkış düğmesi |
| `GET /login` | Girişi başlatır: KeenAuth'a yönlendirir |
| `GET /signin-oidc` | OpenID Connect işleyicisi tarafından sunulur: geri çağırma |
| `GET /dashboard` | Bir oturum gerektirir. Kullanıcıyı ve API'nizin döndürdüğü mesajları gösterir |
| `POST /logout` | Oturumu burada ve KeenAuth'ta sonlandırır. Antiforgery token'ı gerektirir |

## Başlamadan önce

- Bir KeenAuth ortamı. Bu ortamın issuer'ı `https://your-environment.eu.keenauth.app` gibi görünür.
- Ortamda bir web uygulaması ([Uygulama oluşturun](/docs/api/management/applications/create)) ve bir istemci sırrı. İşleyici sırrı token isteğinin gövdesinde gönderir, bu nedenle `client_secret_post` seçin. `development` ortamında `http://localhost/signin-oidc` yönlendirme URI'sini ve `http://localhost/` çıkış sonrası yönlendirme URI'sini kaydedin; loopback URI'si bağlantı noktası olmadan kaydedilir ve `8080` gibi herhangi bir bağlantı noktasıyla eşleşir.
- Ortamda `messages:read` kapsamıyla kayıtlı bir API ([API oluşturun](/docs/api/management/apis/create)). Örneğin `https://api.example.com` olan `identifier` değeri, erişim token'larının hedef kitlesidir (audience). [Express hızlı başlangıcı](/docs/quickstarts/express), bu token'ları kabul eden bir API oluşturur.
- .NET 10 SDK (10.0.403 veya daha yeni bir yama sürümü).

## 1. Paketi ekleyin

Uygulamanın tek bir paketi vardır ve tam bir sürüme sabitlenmiştir. Kilit dosyası (`packages.lock.json`, `RestorePackagesWithLockFile` ile etkinleştirilir) diğer tüm paketleri içerik karmasıyla birlikte sabitler.

```xml
    <!-- The standard OpenID Connect client of ASP.NET Core (MIT). Exact version; the lock file pins the rest. -->
    <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="[10.0.14]" />
```

Bağımlılık grafiğindeki bir değişikliğin sessizce geçmek yerine başarısız olması için geri yüklemeyi kilitli modda yapın:

```sh
dotnet restore --locked-mode
```

## 2. Yapılandırın

Uygulama yapılandırmasını yalnızca ortam değişkenlerinden okur. Değerlerini asla commit etmeyin.

| Değişken | Zorunlu | Anlamı |
|---|---|---|
| `KEENAUTH_ISSUER` | evet | Ortamınızın issuer'ı, `https` |
| `KEENAUTH_CLIENT_ID` | evet | Web uygulamasının `client_id` değeri |
| `KEENAUTH_CLIENT_SECRET` | evet | Web uygulamasının istemci sırrı |
| `APP_BASE_URL` | evet | Uygulamanın herkese açık URL'si. Yönlendirme URI'si bundan oluşturulur. `http` yalnızca `localhost`, `127.0.0.1` veya `[::1]` için kullanılabilir |
| `API_URL` | evet | Uygulamanın çağırdığı API, örneğin `http://localhost:3001` |
| `KEENAUTH_API_AUDIENCE` | evet | KeenAuth'taki bu API'nin tanımlayıcısı |
| `SESSION_SECURE_COOKIE` | hayır | Yalnızca düz `http://localhost` için `false` |

`KeenAuthSettings.From`, bu değerleri başlangıçta okur ve denetler. Eksik ya da kullanılamaz bir değer uygulamayı durdurur ve hiçbir hata mesajı bir değer içermez.

```csharp
    public static KeenAuthSettings From(Func<string, string?> env) => new(
        Url(Need(env, "KEENAUTH_ISSUER"), "KEENAUTH_ISSUER", httpsOnly: true, anyScheme: false),
        Need(env, "KEENAUTH_CLIENT_ID"),
        Need(env, "KEENAUTH_CLIENT_SECRET"),
        Url(Need(env, "APP_BASE_URL"), "APP_BASE_URL", httpsOnly: false, anyScheme: false),
        Url(Need(env, "API_URL"), "API_URL", httpsOnly: false, anyScheme: false),
        // RFC 8707: the access token is requested for your API, which then accepts it as its audience.
        Url(Need(env, "KEENAUTH_API_AUDIENCE"), "KEENAUTH_API_AUDIENCE", httpsOnly: false, anyScheme: true),
        // The session cookie is Secure unless this is explicitly "false" (plain http://localhost).
        !string.Equals(env("SESSION_SECURE_COOKIE"), "false", StringComparison.OrdinalIgnoreCase));
```

## 3. Oturumu sunucuda tutun

Çerez şeması kullanıcıyı uygulamanıza giriş yaptırır. Çerezi yalnızca rastgele bir anahtar taşır; kullanıcı ve token'lar sunucudaki bir bilet deposunda (`MemoryTicketStore`, tek bir sürecin belleğinde) kalır.

```csharp
            .AddCookie(options =>
            {
                // The cookie holds only a random key; the user and the tokens stay in MemoryTicketStore.
                options.Cookie.HttpOnly = true;
                options.Cookie.SameSite = SameSiteMode.Lax;
                options.Cookie.SecurePolicy = settings.SecureCookie ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
                options.ExpireTimeSpan = TimeSpan.FromHours(8);
                options.SlidingExpiration = false;
            })
```

## 4. OpenID Connect işleyicisini yapılandırın

İşleyici, ortamınızın keşif (`<issuer>/.well-known/openid-configuration`) bilgilerini ve imza anahtarlarını okur. PKCE (S256), `state` ve `nonce` ile yetkilendirme kodu akışını kullanır ve dönüşte bunları denetler. Keşif, itilmiş bir yetkilendirme isteği uç noktası belirtirse işleyici bunu kullanır (RFC 9126).

```csharp
                // The endpoints and the signing keys come from discovery: <issuer>/.well-known/openid-configuration.
                options.Authority = settings.Issuer;
                options.ClientId = settings.ClientId;
                options.ClientSecret = settings.ClientSecret;
                options.ResponseType = OpenIdConnectResponseType.Code;
                options.ResponseMode = OpenIdConnectResponseMode.Query;
                // PKCE (RFC 7636) with S256; the handler also sends state and nonce and checks them on the way back.
                options.UsePkce = true;
                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("email");
                // messages:read is a scope of your API; the example API requires it on /api/messages.
                options.Scope.Add("messages:read");
                options.SaveTokens = true;
                options.MapInboundClaims = false;
                options.GetClaimsFromUserInfoEndpoint = false;
                // Pushed authorization requests (RFC 9126) when the environment offers the endpoint.
                options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.UseIfAvailable;
                options.CallbackPath = "/signin-oidc";

                // ID tokens must be signed, with the keys of the environment and one of these algorithms.
                options.TokenValidationParameters.RequireSignedTokens = true;
                options.TokenValidationParameters.ValidateIssuerSigningKey = true;
                options.TokenValidationParameters.ValidAlgorithms = ["RS256", "ES256", "PS256"];
                options.TokenValidationParameters.NameClaimType = "name";

                // These cookies carry the state and the nonce between the redirect and the callback.
                var cookiePolicy = settings.SecureCookie ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
                options.NonceCookie.SameSite = SameSiteMode.Lax;
                options.NonceCookie.SecurePolicy = cookiePolicy;
                options.CorrelationCookie.SameSite = SameSiteMode.Lax;
                options.CorrelationCookie.SecurePolicy = cookiePolicy;
```

İşleyiciyi üç olay tamamlar. Birincisi API'nizin tanımlayıcısını `resource` (RFC 8707) olarak ekler; bu olmadan erişim token'ı API'niz için amaçlanmamış olur ve API bunu reddeder. İkincisi yetkilendirme yanıtının `iss` parametresini denetler (RFC 9207): ASP.NET Core bunu denetlemez, KeenAuth ise bunu duyurur.

```csharp
                    OnRedirectToIdentityProvider = context =>
                    {
                        // Build the redirect URI from the setting, not from the Host header of the request.
                        context.ProtocolMessage.RedirectUri = settings.RedirectUri;
                        // RFC 8707: an access token for your API.
                        context.ProtocolMessage.SetParameter("resource", settings.ApiAudience);
                        return Task.CompletedTask;
                    },
                    OnMessageReceived = async context =>
                    {
                        // RFC 9207: the authorization response must name the issuer that answered.
                        var configuration = await context.Options.ConfigurationManager!
                            .GetConfigurationAsync(context.HttpContext.RequestAborted);
                        var iss = context.ProtocolMessage.Iss;
                        if (iss is not null ? iss != configuration.Issuer : configuration.AuthorizationResponseIssParameterSupported)
                        {
                            context.Fail("The authorization response has a wrong or missing iss parameter.");
                        }
                    },
```

Üçüncü olay, ortamınızın bir anahtarıyla imzalanmamış bir ID token'ı reddeder. ASP.NET Core, token uç noktasından gelen bir ID token'ın imza denetimini atlar; OpenID Connect Core bunu TLS üzerinden izin verir. Bu uygulama yine de denetler.

```csharp
                    OnTokenResponseReceived = async context =>
                    {
                        // ASP.NET Core skips the signature check of an ID token that comes from the token endpoint (OpenID
                        // Connect Core 3.1.3.7 allows it over TLS). This app does not: the token must be signed by a key
                        // of the environment, with an allowed algorithm.
                        var configuration = await context.Options.ConfigurationManager!
                            .GetConfigurationAsync(context.HttpContext.RequestAborted);
                        var parameters = context.Options.TokenValidationParameters.Clone();
                        parameters.ValidIssuer = configuration.Issuer;
                        parameters.IssuerSigningKeys = configuration.SigningKeys;
                        parameters.ValidAudience = settings.ClientId;
                        var result = await new JsonWebTokenHandler().ValidateTokenAsync(
                            context.TokenEndpointResponse.IdToken ?? "", parameters);
                        if (!result.IsValid)
                        {
                            context.Fail("The ID token is not signed by a key of the environment.");
                        }
                    },
```

Son iki olay, çıkış isteğine çıkış sonrası yönlendirme URI'sini ekler ve başarısız bir girişi nedenini göstermeden ana sayfaya geri gönderir:

```csharp
                    OnRedirectToIdentityProviderForSignOut = context =>
                    {
                        context.ProtocolMessage.PostLogoutRedirectUri = settings.PostLogoutRedirectUri;
                        return Task.CompletedTask;
                    },
                    OnRemoteFailure = context =>
                    {
                        // Do not show the reason in the browser; the logs have it.
                        context.Response.Redirect("/?error=signin");
                        context.HandleResponse();
                        return Task.CompletedTask;
                    },
```

## 5. Giriş yapın, bir sayfayı koruyun ve çıkış yapın

`/login` girişi başlatır ve sonrasında sabit bir yerel yol olan panele döner:

```csharp
public class LoginModel : PageModel
{
    /// <summary>Starts sign-in: redirects to KeenAuth, and back to the dashboard afterwards (a fixed local path).</summary>
    public IActionResult OnGet() =>
        Challenge(new AuthenticationProperties { RedirectUri = "/dashboard" }, OpenIdConnectDefaults.AuthenticationScheme);
}
```

Panel bir oturum gerektirir: oturum yoksa ASP.NET Core girişi başlatır. Çıkış bir `POST` isteğidir ve Razor Pages antiforgery token'ını denetler. Oturumu burada sonlandırır, ardından KeenAuth'a yönlendirir (`id_token_hint` ve `post_logout_redirect_uri` ile OpenID Connect RP-Initiated Logout):

```csharp
public class LogoutModel : PageModel
{
    /// <summary>
    /// POST only, and Razor Pages checks the antiforgery token. Ends the session here, then redirects to KeenAuth
    /// (OpenID Connect RP-Initiated Logout with id_token_hint and post_logout_redirect_uri).
    /// </summary>
    public IActionResult OnPost() =>
        SignOut(new AuthenticationProperties(), CookieAuthenticationDefaults.AuthenticationScheme,
            OpenIdConnectDefaults.AuthenticationScheme);
}
```

Ana sayfa girişe bağlanır ve çıkış için POST gönderir; form tag helper antiforgery token'ını ekler:

```cshtml
    @if (User.Identity?.IsAuthenticated == true)
    {
      <p>Signed in as @Model.DisplayName.</p>
      <p><a href="/dashboard">Dashboard</a></p>
      <!-- The form tag helper adds the antiforgery token. -->
      <form method="post" asp-page="/Logout"><button type="submit">Sign out</button></form>
    }
    else
    {
      <p><a href="/login">Sign in</a></p>
    }
```

## 6. API'nizi çağırın

Panel, erişim token'ını oturumdan alır ve API'nizi bu token ile çağırır. Token sunucuda kalır; tarayıcı yalnızca HTML alır.

```csharp
/// <summary>Needs a session. The access token stays on the server; the browser receives only the HTML.</summary>
[Authorize]
public class DashboardModel(MessagesClient messages) : PageModel
{
    public string DisplayName => User.Identity?.Name ?? User.FindFirst("sub")?.Value ?? "";

    public int Status { get; private set; }

    public string? Messages { get; private set; }

    public async Task OnGetAsync()
    {
        var accessToken = await HttpContext.GetTokenAsync("access_token");
        (Status, Messages) = await messages.FetchAsync(accessToken ?? "", HttpContext.RequestAborted);
    }
}
```

```csharp
    public async Task<(int Status, string? Body)> FetchAsync(string accessToken, CancellationToken cancel)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, "api/messages");
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        try
        {
            using var response = await http.SendAsync(request, cancel);
            return ((int)response.StatusCode, response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync(cancel) : null);
        }
        catch (HttpRequestException)
        {
            return (502, null);
        }
    }
```

Örnekte yenileme token'ı yoktur: erişim token'ının süresi dolduğunda API'niz `401` yanıtı verir ve panel bu durumu gösterir. Tekrar giriş yapın.

## 7. Uygulamayı çalıştırın

Hızlı başlangıçtaki Express API'sini (ya da bu token'ları kabul eden herhangi bir API'yi) başlatın ve uygulamayı değişkenler dışa aktarılmış halde çalıştırın:

```sh
ASPNETCORE_URLS=http://localhost:8080 dotnet run --project App
```

`http://localhost:8080` adresini açın, **Giriş yap** seçeneğini seçin, KeenAuth'ta giriş yapın ve paneli açın.

## 8. Ağ olmadan test edin

Testler ağ bağlantısı ve KeenAuth hesabı gerektirmez. Loopback bağlantı noktasındaki küçük bir Kestrel sunucusu ortamınızı (keşif, test çalıştırması için oluşturulmuş bir anahtar kümesi, token uç noktası, itilmiş yetkilendirme istekleri, oturumu sonlandırma uç noktası) ve API'nizi taklit eder. Testler gerçek uygulamayı başka bir bağlantı noktasında başlatır ve yönlendirmeleri takip etmeyen bir HTTP istemcisiyle sürer; bu istemci, okuyabildiğiniz bir tarayıcı gibidir:

```csharp
    [Fact]
    public async Task APageWithoutASessionStartsSignInAndCallsNoApi()
    {
        using var response = await h.Get("/dashboard");
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.StartsWith(h.Fake.Issuer + "/oauth2/authorize?", response.Headers.Location!.ToString());
        Assert.Empty(h.Fake.To("/api/messages"));
    }
```

```sh
dotnet restore Tests --locked-mode
python3 ../scripts/nuget_deps.py    # licences and exact pins (--age: 7-day rule, network)
dotnet test Tests --no-restore
```

Testler şunları denetler: yapılandırmanın başlangıçta doğrulandığını ve hiçbir hatanın bir sır içermediğini; girişin PKCE S256, `state`, `nonce` ve `resource` gönderdiğini ve kodun eşleşen verifier ile değiştirildiğini; itilmiş bir yetkilendirme isteğinin parametreleri tarayıcıdan uzak tuttuğunu; erişim token'ının API'ye `Authorization` başlığıyla gittiğini ve hiçbir zaman bir çereze ya da sayfaya yazılmadığını; bilinmeyen ya da yeniden kullanılan bir state, hatalı ya da eksik bir `iss`, ve imzasız, başka bir anahtarla imzalanmış, genel anahtarla HMAC imzalanmış ya da başka bir hedef kitle, nonce veya issuer için verilmiş ya da süresi dolmuş bir ID token'ın oturum açılmadan reddedildiğini; oturumu olmayan bir sayfanın girişi başlattığını; ve çıkışın antiforgery token'ı gerektirdiğini, `id_token_hint` ile oturumu sonlandırma uç noktasına gittiğini ve oturumu sunucuda sonlandırdığını.

Gerçek bir tarayıcıyla tam giriş bir KeenAuth ortamı gerektirir. Bu, çevrimdışı testlerin bir parçası değildir.

## Proxy veya yük dengeleyicinin arkasında

`APP_BASE_URL` değerini uygulamanın herkese açık URL'sine ayarlayın; yönlendirme URI'si `Host` başlığından değil, bundan oluşturulur. Bir proxy TLS'yi sonlandırıyorsa, oturum çerezleri `Secure` bayrağını alsın diye iletilen başlıklar ara katmanını yalnızca uygulamaya ulaşmanın tek yolu proxy ise ekleyin.

## Bugünkü sınırlar

- Giriş, `/login` adresine bir `GET` isteğiyle başlar. Geri çağırmayı `state` denetimi ve PKCE korur.
- İşleyici istemciyi `client_secret_basic` ile değil, `client_secret_post` ile doğrular.
- Uygulama RS256, ES256 veya PS256 ile imzalanmış ID token'ları kabul eder. KeenAuth EdDSA ile de imzalayabilir; bu örnek bunu kabul etmez ve EdDSA denenmedi. Web uygulamasının ortamı için bu üç algoritmadan birini tutun.
- Bilet deposu ile CSRF token'ını ve state çerezlerini koruyan anahtarlar tek bir sürecin belleğinde durur. Yeniden başlatmadan sonra oturumlar sona erer ve devam eden girişler başarısız olur. Birden fazla sunucuda, biletleri paylaşılan bir önbellekte tutun ve ASP.NET Core Data Protection anahtarlarını kalıcı hale getirin.
- Yenileme token'ı yoktur ve sessiz oturum açma yoktur.

## Sonraki adımlar

- [Express API'sini koruyun](/docs/quickstarts/express): bu uygulamanın çağırdığı API.
- [Token doğrulama](/docs/sdk/token-verification): API'nizin ara katmanının altındaki doğrulayıcı.
