
# React Router uygulamasına giriş ekleme

Bu hızlı başlangıç, framework modunda bir React Router 8 uygulamasına standart OpenID Connect istemcisi `openid-client` ile KeenAuth girişi ekler. Loader'lar ve action'lar sunucuda çalışır; bu nedenle uygulamanız OAuth istemcisidir. Tüm belirteçleri sunucu tarafındaki bir oturumda tutar, tarayıcıya yalnızca HTML ve imzalı bir oturum kimliği çerezi gönderilir. Giriş rotalarını ekleyecek, bir sayfayı koruyacak, korumalı bir API'yi kullanıcının erişim belirteciyle çağıracak, çıkış yapacak ve uygulamayı çevrimdışı test edeceksiniz.

Sunucusuz tek sayfalık bir uygulama için bkz. [React tek sayfalık uygulamasına giriş ekleme](/docs/quickstarts/react).

Uygulamanın rotaları şunlardır:

| Rota | Ne yapar |
|---|---|
| `GET /` | Herkese açık ana sayfa: bir giriş düğmesi veya kullanıcının adı ve bir çıkış düğmesi |
| `GET /dashboard` | Oturum gerektirir. Kullanıcıyı ve API'nizin döndürdüğü iletileri gösterir |
| `POST /login` | Girişi başlatır. Yalnızca bu uygulamanın bir sayfasından |
| `GET /auth/callback` | Yanıtı denetler, kodu değiştirir ve oturumu başlatır |
| `POST /logout` | Oturumu burada ve KeenAuth'ta sonlandırır. Yalnızca bu uygulamanın bir sayfasından |

## Başlamadan önce

- Bir KeenAuth ortamı. Veren adresi `https://your-environment.eu.keenauth.app` biçimindedir.
- Ortamda bir web uygulaması ([Uygulama oluşturma](/docs/api/management/applications/create)) ve bir istemci gizli anahtarı (`client_secret_basic`). `development` ortamında `http://localhost/auth/callback` yönlendirme URI'sini ve `http://localhost/` çıkış sonrası yönlendirme URI'sini kaydedin; loopback URI'si bağlantı noktası olmadan kaydedilir ve `5173` gibi her bağlantı noktasıyla eşleşir.
- Ortamda `messages:read` kapsamıyla kayıtlı bir API ([API oluşturma](/docs/api/management/apis/create)). Örneğin `https://api.example.com` olan `identifier` değeri, erişim belirteçlerinin hedef kitlesidir. [Express hızlı başlangıcı](/docs/quickstarts/express) bu belirteçleri kabul eden bir API oluşturur.
- Node.js 22.22 veya üzeri ve framework modunda bir React Router uygulaması (`npx create-react-router@8.5.0`).

## 1. Kurulum

```sh
npm install --save-exact openid-client@6.8.8
```

`openid-client`, OpenID Connect bağlanan taraf (relying party) işlevlerini uygular: keşif, PKCE ile yetkilendirme kodu akışı, RFC 9207 `iss` denetimi ve ID belirteci denetimleri. Örnek her paketi tam sürümüyle sabitler ve `npm ci --ignore-scripts` ile `package-lock.json` dosyasından kurar; böylece kurulum sırasında hiçbir paket betiği çalışmaz.

## 2. Yapılandırma

Uygulama yapılandırmasını yalnızca ortam değişkenlerinden okur. Bunları `npm run dev` ve `npm start` komutlarının `node --env-file=.env` ile yüklediği `.env` dosyasına koyun ve bu dosyayı asla commit etmeyin.

| Değişken | Zorunlu | Anlamı |
|---|---|---|
| `KEENAUTH_ISSUER` | evet | Ortamınızın veren adresi, `https` |
| `KEENAUTH_CLIENT_ID` | evet | Web uygulamasının `client_id` değeri |
| `KEENAUTH_CLIENT_SECRET` | evet | Onun istemci gizli anahtarı |
| `APP_BASE_URL` | evet | Uygulamanın herkese açık URL'si. Yönlendirme URI'si bundan oluşturulur. Yalnızca `localhost`, `127.0.0.1` veya `[::1]` için `http` |
| `SESSION_SECRET` | evet | En az 32 karakter; oturum çerezini imzalar. `openssl rand -base64 32` ile üretin |
| `API_URL` | evet | Uygulamanın çağırdığı API, örneğin `http://localhost:8080` |
| `KEENAUTH_API_AUDIENCE` | evet | Bu API'nin KeenAuth'taki tanımlayıcısı |

`app/lib/config.server.ts` içindeki `loadConfig`, bu değerleri ilk istekte denetler. Eksik veya kullanılamaz bir değer isteği durdurur ve hiçbir hata iletisi bir değer içermez:

```ts
  const issuer = url("KEENAUTH_ISSUER", false);
  const appBaseUrl = url("APP_BASE_URL", true).origin;
  const sessionSecret = need("SESSION_SECRET");
  if (sessionSecret.length < 32) throw new Error("SESSION_SECRET must be at least 32 characters");
```

`.server.ts` ile biten dosyalar tarayıcı paketine hiçbir zaman ulaşmaz; bir sayfa bunları tarayıcı için içe aktarırsa React Router derlemeyi durdurur.

## 3. OpenID Connect istemcisini oluşturma

`app/lib/keenauth.server.ts`, istemciyi keşfden (`<issuer>/.well-known/openid-configuration`) bir kez oluşturur:

```ts
export function keenauth(): Promise<client.Configuration> {
  discovered ??= client
    .discovery(config().issuer, config().clientId, undefined, client.ClientSecretBasic(config().clientSecret), {
      // Also check the ID token signature with your environment's keys (jwks_uri), not only the TLS connection.
      execute: [client.enableNonRepudiationChecks],
    })
```

Varsayılan olarak `openid-client`, TLS üzerinden doğrudan belirteç uç noktasından gelen bir ID belirtecine güvenir. `enableNonRepudiationChecks`, bu belirtecin imzasını ortamınızın yayımladığı anahtarlarla da denetler.

## 4. Oturumu sunucuda tutma

Oturum sunucudaki dosyalarda yaşar; çerez yalnızca imzalı bir oturum kimliği tutar:

```ts
  storage ??= createFileSessionStorage<SessionData>({
    dir: ".sessions",
    cookie: {
      name: "__keenauth_session",
      httpOnly: true,
      sameSite: "lax",
      // Secure whenever the app is served over https; plain http only for localhost.
      secure: config().redirectUri.startsWith("https:"),
      path: "/",
      maxAge: 8 * 60 * 60,
      secrets: [config().sessionSecret],
    },
  });
```

Dosya deposu tek bir sunucuya hizmet eder. Birden çok sunucuda, `createSessionStorage` ile ve Redis gibi paylaşılan bir depoyla bir oturum deposu yazın. Belirteçler için çerez oturum deposunu kullanmayın: iki belirteç 4 KB çerez sınırını aşabilir ve tarayıcı bunları tutmuş olur.

## 5. Girişi başlatma

Rotalar `app/routes.ts` içinde listelenir:

```ts
export default [
  index("routes/home.tsx"),
  route("dashboard", "routes/dashboard.tsx"),
  route("login", "routes/login.ts"),
  route("auth/callback", "routes/callback.ts"),
  route("logout", "routes/logout.ts"),
] satisfies RouteConfig;
```

Giriş, `/login` adresine gönderen bir formdur:

```tsx
        <Form method="post" action="/login" reloadDocument>
          <button type="submit">Sign in</button>
        </Form>
```

Action, her giriş için yeni bir `state`, `nonce` ve PKCE doğrulayıcısı oluşturur ve bunları oturumda tutar:

```ts
export async function action({ request }: Route.ActionArgs) {
  assertSameOrigin(request);
  const session = await getSession(request);
  const transaction = newTransaction();
  session.set("transaction", transaction);
  const url = await authorizationUrl(transaction);
  return redirect(url.href, { headers: { "Set-Cookie": await sessions().commitSession(session) } });
}
```

Yetkilendirme isteği, doğrulayıcının S256 aşamasını ve API'nizin tanımlayıcısını `resource` (RFC 8707) olarak gönderir. `resource` olmadan erişim belirteci API'niz için düzenlenmez ve API onu reddeder.

```ts
  return client.buildAuthorizationUrl(await keenauth(), {
    redirect_uri: config().redirectUri,
    // messages:read is a scope of your API; the example API requires it on /api/messages.
    scope: "openid profile email messages:read",
    state: transaction.state,
    nonce: transaction.nonce,
    // PKCE (RFC 7636): S256 of the verifier that only this session knows.
    code_challenge: await client.calculatePKCECodeChallenge(transaction.codeVerifier),
    code_challenge_method: "S256",
    // RFC 8707: an access token for your API.
    resource: config().apiAudience,
  });
```

### POST isteğini yalnızca kendi sayfalarınızdan kabul etme

React Router, bir sayfanın action'ına siteler arası gelen POST isteğini reddeder; ancak `/login` ve `/logout` kaynak rotalarıdır (sayfası olmayan action'lar) ve bunlar için denetim sizindir. `assertSameOrigin`, POST isteğinin `Origin` başlığı (yoksa `Referer`) `APP_BASE_URL` adresini gösterdiğinde kabul eder:

```ts
  const app = new URL(config().redirectUri).origin;
  const source = request.headers.get("Origin") ?? request.headers.get("Referer");
```

## 6. Geri dönüşü işleme

Geri dönüş işlemi önce işlemi oturumdan alır; böylece bir `state` yalnızca bir kez çalışır:

```ts
  const old = await getSession(request);
  const transaction = old.get("transaction");
  // The transaction works once: it leaves the session before anything else happens.
  old.unset("transaction");
```

`authorizationCodeGrant`, ortamınızın her yetkilendirme yanıtında gönderdiği `state` ve `iss` değerlerini denetler (RFC 9207), kodu PKCE doğrulayıcısıyla değiştirir ve ID belirtecini denetler: imzası, `iss`, `aud`, `exp`, `iat` ve bu girişin `nonce` değeri. Yönlendirme URI'si `APP_BASE_URL` değerinden oluşturulur, asla `Host` başlığından değil:

```ts
  const callbackUrl = new URL(config().redirectUri);
  callbackUrl.search = new URL(request.url).search;
  return client.authorizationCodeGrant(await keenauth(), callbackUrl, {
    pkceCodeVerifier: transaction.codeVerifier,
    expectedState: transaction.state,
    expectedNonce: transaction.nonce,
    idTokenExpected: true,
  });
```

Başarısız bir denetim sabit bir hata koduyla `401` yanıtı verir. Başarılı girişten sonra uygulama eski oturumu siler ve kullanıcıyı ile belirteçleri yeni bir oturumda tutar:

```ts
  // A new session id after sign-in prevents session fixation: the old session is deleted, a new one is created.
  await sessions().destroySession(old);
  const session = await sessions().getSession(null);
```

## 7. Sayfaları koruma ve API'nizi çağırma

Bir kullanıcı gerektiren loader, `requireUser` işlevini çağırır; bu işlev oturum yoksa kullanıcıyı ana sayfaya yönlendirir. Örnekte yenileme belirteci yoktur: erişim belirteci süresi dolduğunda kullanıcı yeniden giriş yapar.

```ts
export async function loader({ request }: Route.LoaderArgs) {
  const { user, accessToken } = await requireUser(request);
  const api = await fetchMessages(accessToken);
  return { user, api };
}
```

Loader, API'nizi erişim belirteciyle taşıyıcı belirteç (bearer token) olarak çağırır. Belirteç sunucuda kalır; sayfa yalnızca kullanıcıyı ve iletileri alır.

```ts
    response = await fetch(`${config().apiUrl}/api/messages`, {
      headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json" },
    });
```

## 8. Çıkış yapma

Çıkış, bu uygulamanın bir sayfasından yapılan bir `POST` isteğidir. Action yerel oturumu sonlandırır ve tarayıcıyı, uygulamanıza geri döndüren `id_token_hint`, `client_id` ve `post_logout_redirect_uri` parametreleriyle ortamınızın çıkış uç noktasına (OpenID Connect RP-Initiated Logout) gönderir:

```ts
  const session = await getSession(request);
  const idToken = session.get("idToken");
  const destroyed = await sessions().destroySession(session);
  return redirect((await logoutUrl(idToken)).href, { headers: { "Set-Cookie": destroyed } });
```

## 9. Uygulamayı çalıştırma

Express hızlı başlangıcındaki API'yi (ya da belirteçleri kabul eden herhangi bir API'yi) başlatın ve uygulamayı çalıştırın:

```sh
npm run dev
```

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

## 10. Ağ olmadan test etme

Testler ağ bağlantısı ve KeenAuth hesabı gerektirmez. `npm test` uygulamayı derler ve derlemeyi React Router'ın istek işleyicisi üzerinden çalıştırır. Sahte bir `fetch`, ortamınızı (keşif, test çalıştırması için oluşturulmuş bir anahtar kümesi, belirteç uç noktası) ve API'yi canlandırır.

```ts
test("callback: an unknown or reused state is refused before any token request and starts no session", async () => {
  const b = browser();
  await startSignIn(b);
  const wrong = await returnFromKeenAuth(b, { state: "not-the-state" });
```

```sh
npm test
npm run deps    # exact pins, lockfile sources and licences
```

Testler şunları denetler: yapılandırmanın doğrulandığını (ve hiçbir hata iletisinde gizli bilginin bulunmadığını); girişin PKCE S256, `state`, `nonce` ve `resource` gönderdiğini ve başka bir siteden ya da `GET` ile gelen isteğin reddedildiğini; kodun eşleşen doğrulayıcı ve istemci kimlik doğrulamasıyla değiştirildiğini; erişim belirtecinin `Authorization` başlığında API'ye gittiğini ve asla tarayıcıya gitmediğini; bilinmeyen veya yeniden kullanılan bir `state`, eksik veya yanlış bir `iss`, bir yetkilendirme hatası ve başka bir istemciye, başka bir nonce ile veya başka bir anahtarla imzalanmış bir ID belirtecinin oturum olmadan reddedildiğini; girişin yeni bir oturum kimliği başlattığını; oturumu olmayan bir sayfanın ana sayfaya yönlendirildiğini; ve çıkışın başka bir siteden reddedildiğini ve `id_token_hint` ile çıkış uç noktasına gittiğini.

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 ve Origin denetimi bunu kullanır, asla `Host` başlığını kullanmaz. Bir proxy TLS'yi sonlandırıyorsa `APP_BASE_URL` yine `https` URL'si olur, böylece oturum çerezi `Secure` kalır.

## Bugünkü sınırlar

- Yenileme belirteci ve sessiz giriş yoktur: erişim belirteci süresi dolduğunda kullanıcı yeniden giriş yapar.
- Dosya oturum deposu belirteçleri `.sessions/` altındaki düz dosyalarda tutar ve süresi dolmuş dosyaları silmez. Bu dizini koruyun ya da oturumları şifreleyen ve süresini dolduran paylaşılan bir depo kullanın.
- İstemci, bir istemci gizli anahtarıyla kimlik doğrular. `openid-client`, `private_key_jwt` yöntemini de destekler; örnek bunu göstermez.

## Sonraki adımlar

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