
# Go net/http oturumları

Paket `github.com/keenauth/keenauth-go/bff`, bir Go `net/http` uygulamasına Giriş yap özelliği ekler. Sunucunuz OAuth istemcisidir ve tüm belirteçleri sunucuda tutar. Tarayıcı yalnızca şifreli, HttpOnly bir oturum çerezi alır ve hiçbir zaman erişim, yenileme veya ID belirteci görmez. Bu, backend-for-frontend (BFF) desenidir. Paket yalnızca standart kütüphaneyi kullanır. Henüz yayımlanmamıştır.

Taşıyıcı belirteç (bearer token) alan bir API'yi korumak için bunun yerine [API'nizi koruma](/docs/sdk/protect-api) sayfasına bakın.

## Başlamadan önce

1. Panelde, ortamınızda bir web uygulaması oluşturun.
2. İstemci kimlik doğrulamasını seçin: bir istemci sırrı (`bff.ClientSecretBasic`) veya `private_key_jwt` (`bff.PrivateKeyJWT`). `private_key_jwt` için uygulamanın genel anahtarını kaydedin ve özel anahtarı sunucunuzda tutun.
3. `https://app.acme.example/auth/callback` yönlendirme URI'sini ve `https://app.acme.example/` çıkış sonrası yönlendirme URI'sini ekleyin.
4. En az 32 karakterlik bir çerez sırrı üretin, örneğin `openssl rand -base64 32` ile. Bunu depoda değil, gizli bilgi deponuzda saklayın.

## Kurulum

```go
package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/keenauth/keenauth-go/bff"
)

func main() {
	auth, err := bff.New(bff.Options{
		Issuer:     "https://acme.eu.keenauth.app", // your environment's issuer, exactly
		ClientID:   "app_01j9zzzzzzzzzzzzzzzzzzzz",
		ClientAuth: bff.ClientSecretBasic{Secret: os.Getenv("KEENAUTH_CLIENT_SECRET")},
		AppBaseURL: "https://app.acme.example",
		Secrets:    []string{os.Getenv("KEENAUTH_COOKIE_SECRET")},
	})
	if err != nil {
		panic(err) // configuration errors stop the app at start-up
	}

	mux := http.NewServeMux()
	mux.Handle("GET /orders", auth.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		s := bff.FromContext(r.Context()).Session
		fmt.Fprintf(w, "Signed in as %s", s.User.Email)
	})))

	http.ListenAndServe(":8080", auth.Middleware(mux))
}
```

İstemci sırrı yerine `private_key_jwt` kullanmak için özel anahtarı geçirin. Paket her belirteç ve PAR isteği için yeni bir istemci onayı (assertion) imzalar (`iss` ve `sub` istemci kimliğinizdir, `aud` yayıncıdır, ömrü 2 dakikadır ve her seferinde yeni bir `jti` kullanılır):

```go
pemBytes, err := os.ReadFile("/run/secrets/keenauth-app-key.pem")
if err != nil {
	panic(err)
}
key, err := assertion.ParsePrivateKeyPEM(pemBytes) // import "github.com/keenauth/keenauth-go/assertion"
if err != nil {
	panic(err)
}
auth, err := bff.New(bff.Options{
	Issuer:     "https://acme.eu.keenauth.app",
	ClientID:   "app_01j9zzzzzzzzzzzzzzzzzzzz",
	ClientAuth: bff.PrivateKeyJWT{Key: key, KeyID: "app-key-2026-11"},
	AppBaseURL: "https://app.acme.example",
	Secrets:    []string{os.Getenv("KEENAUTH_COOKIE_SECRET")},
})
```

ES256 (P-256), EdDSA (Ed25519), RS256 ve PS256 (RSA, 2048 bit veya daha fazla) desteklenir; algoritma anahtarı takip eder, `Algorithm: "PS256"` ise RSA anahtarı için PSS'i seçer. `Key` herhangi bir `crypto.Signer` olabilir, dolayısıyla bir KMS veya HSM'de tutulan anahtar da çalışır. Onayı başka bir yerde imzalıyorsanız, bunun yerine `bff.PrivateKeyJWT{Assertion: f}` ayarlayın; burada `f` her çağrıda yeni bir imzalı onay döndürür. `Key` ve `Assertion` alanlarından yalnızca birini ayarlayın, ikisini birden değil.

`AppBaseURL` uygulamanızın genel adresidir. Paket yönlendirme URI'sini ve tüm yönlendirmeleri bundan oluşturur; isteğin `Host` veya `X-Forwarded-*` başlıklarından asla oluşturmaz. Düz `http` yalnızca `localhost` için kabul edilir.

## Rotalar

`Middleware` üç rota sunar ve diğer her istekte oturumu okur. Rotaları tek başına da `mux.Handle("/auth/", auth)` ile bağlayabilirsiniz.

| Rota | Ne yapar |
|---|---|
| `GET /auth/login` | Giriş işlemini başlatır. İsteğe bağlı sorgu parametreleri: `returnTo` (kullanıcının varacağı yer) ve `organization` (bir kuruluşta oturum açma). |
| `GET /auth/callback` | Girişi tamamlar ve oturum çerezini ayarlar. |
| `GET` veya `POST /auth/logout` | KeenAuth'ta Çıkış yap işlemini gerçekleştirir ve uygulamanıza geri döner. |

Giriş, PKCE (S256) ile yetkilendirme kodu akışını kullanır. Ortamınız sunduğunda istek sunucuya gönderilir (PAR). Geri çağırmada paket `state`, `iss` ve ID belirtecini, `nonce` dahil, denetler. Her başarılı girişte yeni bir oturum oluşturulur; eski oturum atılır.

`returnTo`, `/orders?tab=open` gibi uygulamanızın kendi yolu olmalıdır. `//evil.example` veya tam bir URL gibi başka her değer yok sayılır ve kullanıcı `/` sayfasına yönlendirilir.

## İşleyicileri koruma

`auth.RequireAuth(handler)` yalnızca oturumu olan isteklerin geçmesine izin verir:

- oturumu olmayan bir sayfa isteği (`GET` veya `HEAD`), `returnTo` ile `/auth/login` adresine yönlendirilir;
- `/api/…` yoluna veya başka bir yönteme yapılan istek, bir sorun belgesiyle birlikte `401` alır. Her yolda `401` yanıtı için `bff.GuardOptions{API: true}` geçirin.

`POST`, `PUT`, `PATCH`, `DELETE` ve diğer güvenli olmayan yöntemlerde tarayıcı isteğin uygulamanızdan geldiğini belirtmelidir: `Sec-Fetch-Site: same-origin` veya uygulamanızın kaynağına eşit bir `Origin` başlığı. Diğer istekler `403` alır. Bu, `SameSite=Lax` ile birlikte siteler arası istek sahteciliğine karşı koruma sağlar.

## Oturumu kullanma ve API'nizi çağırma

`bff.FromContext(r.Context())` şunları döndürür:

- `Session`: `nil` veya oturum açmış kullanıcı. `Session.User`, `Subject`, `Email`, `Name`, `OrgID` ve `OrgRoles` gibi tipli ID belirteci taleplerine sahiptir; tüm talepler `Session.User.Claims` içindedir. Oturum asla bir belirteç içermez.
- `AccessToken`: sunucu tarafı çağrılar için geçerli bir erişim belirteci veya `""`. Bunu tarayıcıya göndermeyin.

Erişim belirtecinin süresi 60 saniye içinde dolacaksa, paket işleyicinizi çalıştırmadan önce belirteci yeniler. Her yenileme yenileme belirtecini döndürür ve yanıta yeni oturum çerezini ayarlar. Sunucu yenileme belirtecini reddederse oturum sona erer. Geçici bir sunucu hatası oturumu değiştirmeden bırakır. Bir süreçteki eş zamanlı istekler tek bir yenilemeyi paylaşır.

## Kuruluş değiştirme

`auth.SwitchOrganizationPath("org_01j9…", "/dashboard")`, kullanıcıyı o kuruluşta yeniden oturum açtıran bir `/auth/login` bağlantısı döndürür. KeenAuth üyeliği denetler. Girişten sonra `Session.User.OrgID` kuruluşu tutar.

## Çıkış yapma

Bir formdan `/auth/logout` adresine `POST` gönderin. Paket oturum çerezini siler (ve bir depo kullanıyorsanız saklanan oturumu da), yenileme belirtecini iptal eder ve tarayıcıyı ortamınızın çıkış uç noktasına yönlendirir; bu uç nokta da tarayıcıyı uygulamanıza geri gönderir. Tarayıcının başka bir siteden geldiğini işaretlediği istek reddedilir. `Sec-Fetch-Site` göndermeyen tarayıcılar yalnızca kendi sayfalarınızdan gelen bir `POST` ile çıkış yapabilir.

## Oturum depolama

Varsayılan olarak tüm oturum çerezdedir; sırrdan türetilen bir anahtarla AES-256-GCM kullanılarak şifrelenir ve kimliği doğrulanır. Değiştirilmiş veya kopyalanmış çerez reddedilir. Çerezin adı `__Host-keenauth_session.0` olup `Secure`, `HttpOnly`, `SameSite=Lax` ve `Path=/` ile ayarlanır. `http://localhost` üzerinde `__Host-` öneki ve `Secure` kullanılmaz. Büyük oturumlar en fazla üç çereze bölünür.

Belirteçleri sunucuda tutmak için `Options.Store` değerini bir `bff.SessionStore` (`Get`, `Set`, `Delete`) olarak ayarlayın; örneğin Redis üzerinde. Bu durumda çerez yalnızca şifreli, rastgele bir oturum kimliği tutar. Her girişte yeni bir kimlik oluşturulur ve önceki kayıt silinir. Depo belirteçleri tuttuğu için erişimini kısıtlayın. `bff.MemoryStore` yalnızca geliştirme ve testler içindir.

Bir oturum en fazla 7 gün sürer (`SessionMaxAge`); yenileme bunu uzatmaz. Sırrı değiştirmek için yeni sırrı önce listeleyin: `Secrets: []string{newSecret, oldSecret}`. Eski sırrı `SessionMaxAge` süresinden sonra kaldırın.

## Bugünkü sınırlar

- DPoP'a bağlı belirteçler ve arka kanal çıkışı henüz desteklenmiyor.
- Yenilemeler yalnızca tek bir sunucu sürecinde paylaşılır. Birden çok örnek varsa, `refresh_reuse_interval_s` uygulama ayarı (varsayılan 5 saniye) paralel yenilemenin başarılı olmasına izin verir; değeri `0` yaparsanız ikinci yenileme oturumu sonlandırır.
