
# Fastify oturumları

`@keenauth/node/bff` paketi, bir Fastify 5 uygulamasına giriş ekler. Sunucunuz OAuth istemcisidir ve tüm belirteçleri sunucuda tutar. Tarayıcıya yalnızca şifrelenmiş, HttpOnly bir oturum çerezi gönderilir; tarayıcı hiçbir erişim, yenileme veya ID belirtecini görmez. Bu, arka uç-ön uç (BFF) desenidir. Paket Fastify'ı içe aktarmaz, bu nedenle ek bir bağımlılık eklemez. Henüz npm'de yayımlanmamıştır.

Taşıyıcı (bearer) belirteçleri 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ı (`client_secret_basic`) ya da genel anahtarını uygulamaya kaydettiğiniz bir anahtar çifti ile `private_key_jwt`. Paylaşılan bir sır sunucunuzdan çıkmadığı için `private_key_jwt` önerilir.
3. Yönlendirme URI'sini `https://app.acme.example/auth/callback` ve çıkış sonrası yönlendirme URI'sini `https://app.acme.example/` olarak ekleyin.
4. En az 32 karakterlik bir çerez sırrı oluşturun; örneğin `openssl rand -base64 32` ile. Bunu depoya değil, gizli bilgi deponuzda saklayın.

## Kurulum

```ts
import Fastify from "fastify";
import { createBff, fastifyRequireAuth, fastifySession } from "@keenauth/node/bff";

const bff = createBff({
  issuer: "https://acme.eu.keenauth.app", // your environment's issuer, exactly
  clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
  clientAuth: { method: "client_secret_basic", clientSecret: process.env.KEENAUTH_CLIENT_SECRET! },
  appBaseUrl: "https://app.acme.example",
  secret: process.env.KEENAUTH_COOKIE_SECRET!,
});

const app = Fastify();
fastifySession(app, bff); // on the root instance, so it applies to every route

app.get("/orders", { preHandler: fastifyRequireAuth(bff) }, async (request) => {
  return `Signed in as ${request.keenauth!.session!.user.email}`;
});
```

`private_key_jwt` kullanırken sır yerine özel anahtarı geçirin: `clientAuth: { method: "private_key_jwt", privateKey: readFileSync("bff-key.pem", "utf8") }`. SDK, belirteç ve PAR uç noktalarına yapılan her istek için yeni bir istemci onayı imzalar. Anahtar bir KMS veya HSM'de tutuluyorsa, `assertion()` her çağrıldığında yeni bir imzalı istemci onayı döndüren `clientAuth: { method: "private_key_jwt", assertion }` değerini geçirin. Onayı ve desteklenen algoritmaları [Express sayfası](/docs/sdk/express) açıklar.

`fastifySession` işlevini kök örnekte çağırın. Kapsüllenmiş bir eklentinin içinde çağrılırsa yalnızca o eklentinin yollarında geçerli olur.

`appBaseUrl`, uygulamanızın herkese açık adresidir. SDK yönlendirme URI'sini ve tüm yönlendirmeleri bundan oluşturur; istekteki `Host` veya `X-Forwarded-*` başlıklarından asla oluşturmaz. Bu nedenle `trustProxy` seçeneği buna etki etmez. Düz `http` yalnızca `localhost` için kabul edilir.

Seçenekler `createBff` çalıştığında denetlenir; dolayısıyla bir yapılandırma hatası uygulamayı başlangıçta durdurur.

## Yollar

`fastifySession` üç yol ekler ve diğer her istekte bir `onRequest` kancasında oturumu okur:

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

Giriş, PKCE (S256) ile yetkilendirme kodu akışını kullanır. Ortamınız destekliyorsa istek sunucuya gönderilir (PAR). Geri çağırmada SDK `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`, uygulamanıza ait bir yol olmalıdır, örneğin `/orders?tab=open`. `//evil.example` ya da tam bir URL gibi başka her değer yok sayılır ve kullanıcı `/` sayfasına yönlendirilir.

## Yolları koruma

`fastifyRequireAuth(bff)`, yalnızca oturumu olan isteklerin geçmesine izin veren bir `preHandler`'dır:

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

`POST`, `PUT`, `PATCH`, `DELETE` ve diğer güvensiz yöntemler için tarayıcı isteğin uygulamanızdan geldiğini belirtmelidir: `Sec-Fetch-Site: same-origin` ya da uygulamanızın kaynağına eşit bir `Origin` başlığı. Diğer istekler `403` alır. Bu, `SameSite=Lax` korumasına ek olarak siteler arası istek sahteciliğine karşı korur.

## Oturumu kullanın ve API'nizi çağırın

`request.keenauth` şunları içerir:

- `session`: `null` veya oturum açmış kullanıcı. `session.user`, `subject`, `email`, `name`, `orgId` ve `orgRoles` gibi türlendirilmiş ID belirteci iddialarını içerir; tüm iddialar `session.user.claims` içindedir. Oturum nesnesi hiçbir zaman belirteç içermez.
- `accessToken`: sunucu tarafı çağrılar için geçerli bir erişim belirteci ya da `null`.

```ts
app.get("/api/orders", { preHandler: fastifyRequireAuth(bff) }, async (request, reply) => {
  const upstream = await fetch("https://api.acme.example/v1/orders", {
    headers: { Authorization: `Bearer ${request.keenauth!.accessToken}` },
  });
  return reply.code(upstream.status).send(await upstream.json());
});
```

TypeScript'te özelliği bir kez bildirin:

```ts
import type { SessionContext } from "@keenauth/node/bff";

declare module "fastify" {
  interface FastifyRequest {
    keenauth?: SessionContext | null;
  }
}
```

Erişim belirtecinin süresi 60 saniye içinde dolacaksa SDK, işleyiciniz çalışmadan ö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.

Erişim belirtecini tarayıcıya göndermeyin.

## Kuruluş değiştirme

`bff.switchOrganizationPath("org_01j9…", "/dashboard")`, kullanıcıyı o kuruluşta yeniden giriş yaptıran `/auth/login` bağlantısını döndürür. KeenAuth üyeliği denetler. Girişten sonra `session.user.orgId` kuruluşu içerir.

## Çıkış yapma

Bir formdan `/auth/logout` adresine `POST` gönderin. SDK şunları yapar:

1. oturum çerezini siler (bir depo kullanıyorsanız saklanan oturumu da);
2. yenileme belirtecini iptal eder;
3. tarayıcıyı, sizi uygulamanıza geri döndüren ortamınızın çıkış uç noktasına 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 gönderilen bir `POST` ile çıkış yapabilir.

## Oturum depolama

Varsayılan olarak tüm oturum çerezdedir; `secret` değerinden türetilen bir anahtarla AES-256-GCM kullanılarak şifrelenir ve doğrulanır. Değiştirilmiş ya da kopyalanmış çerez reddedilir. Çerez 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 bir depo geçirin:

```ts
import { createBff, type SessionStore } from "@keenauth/node/bff";

const store: SessionStore = {
  get: async (id) => JSON.parse((await redis.get(`sess:${id}`)) ?? "null"),
  set: async (id, session, ttl) => void (await redis.set(`sess:${id}`, JSON.stringify(session), { EX: ttl })),
  delete: async (id) => void (await redis.del(`sess:${id}`)),
};
const bff = createBff({ /* … */ store });
```

Bu durumda çerez yalnızca şifrelenmiş rastgele bir oturum kimliği içerir. Her giriş yeni bir kimlik oluşturur ve önceki kaydı siler. Depo belirteç içerdiği için erişimi kısıtlayın. `MemorySessionStore` yalnızca geliştirme ve testler içindir.

Oturum en fazla 7 gün sürer (`sessionMaxAge`); yenileme bu süreyi uzatmaz. Sırrı değiştirmek için bir liste geçin: `secret: [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.
