---
title: 'Temel Kimlik Doğrulama'
source: 'https://academia.sh/tr/kurslar/kimlik-ve-yetki/temel-kimlik-dogrulama'
course: 'Kimlik Doğrulama ve Yetkilendirme'
language: tr
updated: '2026-08-17T18:06:48+00:00'
license: 'CC BY-SA 4.0'
---

# Temel Kimlik Doğrulama

HTTP'nin kendi içinde tanımlı temel şemasının kurulması: 401 daveti ve WWW-Authenticate başlığı, taşınan değerin çözülebilir bir kodlama olduğunun gösterilmesi, sabit zamanlı karşılaştırma ve kimlik bilgisinin her istekte geçmesinin sonuçları.

Önceki derste kimlik, bir eşleme tablosunun arkasına saklanmıştı: istek bir dizge taşıdı,
sunucu onu bir asıla çevirdi. O dizgenin nereden geldiği sorusu açık kalmıştı. HTTP bu soruya
kendi içinde bir yanıt verir ve yanıtın en küçüğü **temel şemadır** (Basic authentication
scheme): kullanıcı adı ile parola, her istekte `Authorization` başlığında taşınır.

Şema küçüktür ve tam olarak bu yüzden öğreticidir. Kimlik doğrulamanın bütün parçalarını
görünür kılar: sunucunun kimlik bilgisini nasıl istediği, istemcinin onu nasıl yolladığı,
sunucunun nasıl karşılaştırdığı ve kimlik bilgisinin ne kadar süre ortalıkta durduğu. Sonraki
derslerin çözdüğü sorunların çoğu, bu şemanın çözmediği sorunlardır.

## Şemanın İki Adımı

Şema bir davetle başlar. İstek kimlik bilgisi taşımıyorsa sunucu 401 döner ve
`WWW-Authenticate` başlığında hangi şemayı beklediğini bildirir. Başlıktaki `realm`
parametresi, kimlik bilgisinin geçerli olduğu **bölgeyi** adlandırır; aynı sunucuda birden
çok bölge bulunabilir ve istemci hangi bölge için hangi bilgiyi kullanacağını bu adla ayırır.
`charset="UTF-8"` parametresi ise kullanıcı adı ve parolanın hangi kodlamayla baytlara
çevrileceğini söyler.

İkinci adımda istemci `Authorization: Basic <değer>` başlığıyla döner. Değer,
`kullanıcı:parola` dizgesinin base64 kodlamasıdır.

```js
// temel.mjs — HTTP temel kimlik dogrulama semasi (RFC 7617)
// Kullanim: node temel.mjs <port>
import { createServer } from "node:http";
import { timingSafeEqual } from "node:crypto";

const PORT = Number(process.argv[2] ?? 8511);

// Gercek kurulumda parola dogrudan degil, dogrulayici olarak saklanir;
// bu ders semayi anlatir, saklamayi degil.
const HESAPLAR = new Map([
  ["raf-terminali", "Kt-9f3a-Raf!"],
  ["deniz.aral", "üç-kitap-1876"],
]);

// Sabit zamanli karsilastirma: esitsizlik kadar uzunluk farki da sizdirmasin
const esitMi = (a, b) => {
  const x = Buffer.from(a, "utf8"), y = Buffer.from(b, "utf8");
  return x.length === y.length && timingSafeEqual(x, y);
};

let istekSayaci = 0, parolaGorulmesi = 0;

createServer((istek, yanit) => {
  yanit.sendDate = false;
  istekSayaci++;
  const json = (kod, govde, baslik = {}) => {
    yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8", ...baslik });
    yanit.end(JSON.stringify(govde));
  };
  const reddet = () => json(401, { hata: "kimlik dogrulanamadi" },
    { "www-authenticate": 'Basic realm="odunc", charset="UTF-8"' });

  const baslik = istek.headers.authorization ?? "";
  if (!baslik.startsWith("Basic ")) return reddet();

  // Basic <base64(kullanici:parola)> — cozme icin anahtar gerekmez
  const cozulmus = Buffer.from(baslik.slice(6), "base64").toString("utf8");
  const ayrac = cozulmus.indexOf(":");
  if (ayrac < 0) return reddet();
  const kullanici = cozulmus.slice(0, ayrac), parola = cozulmus.slice(ayrac + 1);
  parolaGorulmesi++;                        // parola bu istekte de sunucuya ulasti

  const beklenen = HESAPLAR.get(kullanici);
  if (beklenen === undefined || !esitMi(parola, beklenen)) return reddet();

  if (istek.url === "/sayac") return json(200, { istek: istekSayaci, parolaGorulmesi });
  json(200, { kullanici, kaynak: istek.url });
}).listen(PORT, "127.0.0.1", () => console.log(`temel 127.0.0.1:${PORT}`));
```

```bash
node temel.mjs 8511 > /dev/null & SUNUCU=$!
sleep 1
echo "--- kimlik bilgisi yok"
curl -s -D - -o /dev/null http://127.0.0.1:8511/odunc/O-1 | head -3
echo "--- curl -u ile"
curl -s -w " %{http_code}\n" -u "raf-terminali:Kt-9f3a-Raf!" http://127.0.0.1:8511/odunc/O-1
echo "--- curl in yolladigi baslik"
curl -s -o /dev/null -u "raf-terminali:Kt-9f3a-Raf!" -v http://127.0.0.1:8511/odunc/O-1 2>&1 | grep -i "^> authorization"
echo "--- yanlis parola"
curl -s -w " %{http_code}\n" -u "raf-terminali:yanlis" http://127.0.0.1:8511/odunc/O-1
echo "--- turkce karakterli parola"
curl -s -w " %{http_code}\n" -u "deniz.aral:üç-kitap-1876" http://127.0.0.1:8511/odunc/O-1
kill $SUNUCU
```

```
--- kimlik bilgisi yok
HTTP/1.1 401 Unauthorized
content-type: application/json; charset=utf-8
www-authenticate: Basic realm="odunc", charset="UTF-8"
--- curl -u ile
{"kullanici":"raf-terminali","kaynak":"/odunc/O-1"} 200
--- curl in yolladigi baslik
> Authorization: Basic cmFmLXRlcm1pbmFsaTpLdC05ZjNhLVJhZiE=
--- yanlis parola
{"hata":"kimlik dogrulanamadi"} 401
--- turkce karakterli parola
{"kullanici":"deniz.aral","kaynak":"/odunc/O-1"} 200
```

Türkçe karakter içeren parola geçti, çünkü hem istemci hem sunucu UTF-8 varsaydı. Bu varsayım
başlıkta yazmasaydı iki taraf farklı baytlar üretebilir ve doğru parola reddedilebilirdi;
`charset` parametresinin işlevi budur.

## Taşınan Değerin Ne Olduğu

Başlıktaki değer bir kodlamadır, şifreleme değildir. Kodlamanın işlevi, keyfi baytları
başlıkta taşınabilecek karakterlere çevirmektir; gizlemek değildir. Bunu göstermek için
sunucuya gerek yok — yukarıdaki `curl` çıktısının ürettiği başlığın kendisi yeter.

```js
// cozum.mjs — temel semanin tasidigi degerin ne olduguna bakar
// Baslik degeri, curl -u "raf-terminali:Kt-9f3a-Raf!" ile uretilenin aynisidir.
const BASLIK = "Basic cmFmLXRlcm1pbmFsaTpLdC05ZjNhLVJhZiE=";

const govde = BASLIK.slice(6);
console.log("taşınan değer :", govde);
console.log("çözülmüş      :", Buffer.from(govde, "base64").toString("utf8"));
console.log("çözmek için gereken anahtar:", "yok");

// Ters yon: ayni degeri uretmek icin de anahtar gerekmiyor
const yeniden = Buffer.from("raf-terminali:Kt-9f3a-Raf!", "utf8").toString("base64");
console.log("yeniden üretilen:", yeniden, "| aynı mı:", yeniden === govde);
```

```
taşınan değer : cmFmLXRlcm1pbmFsaTpLdC05ZjNhLVJhZiE=
çözülmüş      : raf-terminali:Kt-9f3a-Raf!
çözmek için gereken anahtar: yok
yeniden üretilen: cmFmLXRlcm1pbmFsaTpLdC05ZjNhLVJhZiE= | aynı mı: true
```

Bu çıktının tek bir sonucu vardır: parolanın gizliliği tümüyle taşıma katmanına bırakılmıştır.
İnternet Nasıl Çalışır kursundaki HTTPS'in Rolü dersinde kurulan gizlilik ve bütünlük
güvenceleri olmadan, temel şema parolayı ağa okunur biçimde koyar. Şifresiz bir bağlantı
üzerinde bu şemayı açan bir yapılandırma reddedilmelidir; şemanın kendisinde bunu telafi
edecek hiçbir parça yoktur.

Aynı sonucun ikinci bir yüzü vardır: parola, sunucuya kadar giden yol boyunca **her ara
bileşende** okunabilir biçimdedir. Ters vekil sunucu, erişim günlüğü, hata izleme kaydı,
tarayıcı geliştirme aracı — hepsi bu başlığı görür. Erişim günlüğüne bütün başlıkları yazan
bir kurulum, parolayı diske de yazmış olur.

```js
// gunluk.mjs — istek gunlugunun kimlik bilgisini nasil sizdirdigi ve nasil kapatildigi
// Kullanim: node gunluk.mjs <port>   (istekler stdout'a gunluk satiri yazar)
import { createServer } from "node:http";

const PORT = Number(process.argv[2] ?? 8512);
const GIZLENECEK = new Set(["authorization", "cookie", "proxy-authorization"]);

const hamSatir = (istek) =>
  JSON.stringify({ yol: istek.url, baslik: istek.headers });

const gizliSatir = (istek) => {
  const baslik = Object.fromEntries(
    Object.entries(istek.headers).map(([ad, deger]) => [ad, GIZLENECEK.has(ad) ? "<gizlendi>" : deger]),
  );
  return JSON.stringify({ yol: istek.url, baslik });
};

createServer((istek, yanit) => {
  yanit.sendDate = false;
  console.log("ham    :", hamSatir(istek));
  console.log("gizli  :", gizliSatir(istek));
  yanit.writeHead(200, { "content-type": "application/json; charset=utf-8" });
  yanit.end('{"tamam":true}');
}).listen(PORT, "127.0.0.1");
```

```bash
node gunluk.mjs 8512 & SUNUCU=$!
sleep 1
curl -s -o /dev/null -H "user-agent: raf-terminali/1" -u "raf-terminali:Kt-9f3a-Raf!" http://127.0.0.1:8512/odunc/O-1
sleep 0.3
kill $SUNUCU
```

```
ham    : {"yol":"/odunc/O-1","baslik":{"host":"127.0.0.1:8512","authorization":"Basic cmFmLXRlcm1pbmFsaTpLdC05ZjNhLVJhZiE=","accept":"*/*","user-agent":"raf-terminali/1"}}
gizli  : {"yol":"/odunc/O-1","baslik":{"host":"127.0.0.1:8512","authorization":"<gizlendi>","accept":"*/*","user-agent":"raf-terminali/1"}}
```

Gizleme listesi başlık adıyla çalışır ve `authorization` dışında `cookie` ile
`proxy-authorization` başlıklarını da kapsar; üçü de kimlik bilgisi taşır. Bu liste,
günlükleme kurulumunun kendisinde bulunmalıdır — her günlük çağrısında elle uygulanan bir
kural, ilk unutulan çağrıda delinir.

## Karşılaştırmanın Sabit Zamanlı Olması

Sunucudaki `esitMi` işlevi parolayı `===` ile karşılaştırmaz. Dizge karşılaştırması ilk
farklı karakterde durur; bu, karşılaştırmanın süresini doğru önek uzunluğuna bağlar. Süre
ölçülebilir bir kanaldır ve doğru öneki adım adım ortaya çıkarabilir.

`timingSafeEqual` bütün baytları her durumda karşılaştırır ve süreyi girdinin içeriğinden
bağımsız kılar. İşlev eşit uzunluk ister; bu yüzden uzunluk denetimi ayrı yapılır ve
uzunluk farkı zaten gizli bir bilgi sayılmaz. Kural, kimlik bilgisiyle ilgili her
karşılaştırma için geçerlidir: parola, oturum kimliği, imza, API anahtarı, tek kullanımlık
kod. Frontend Kalitesi kursundaki istek belirteci karşılaştırması da aynı kuralı uygular.

## Kimlik Bilgisinin Her İstekte Geçmesi

Temel şemanın en belirleyici özelliği, sunucunun istekler arasında hiçbir şey
hatırlamamasıdır. Kimlik doğrulama her istekte sıfırdan yapılır, dolayısıyla parola her
istekte yeniden yollanır. Sunucudaki `parolaGorulmesi` sayacı bunu ölçer.

```bash
node temel.mjs 8511 > /dev/null & SUNUCU=$!
sleep 1
for yol in /odunc/O-1 /odunc/O-2 /kitaplar/978-0262033848 /uyeler/U-1001; do
  curl -s -o /dev/null -u "raf-terminali:Kt-9f3a-Raf!" "http://127.0.0.1:8511$yol"
done
curl -s -u "raf-terminali:Kt-9f3a-Raf!" http://127.0.0.1:8511/sayac
echo
kill $SUNUCU
```

```
{"istek":5,"parolaGorulmesi":5}
```

Beş istek, parolanın beş kez ağa çıkması demektir. Bunun üç sonucu vardır.

Birincisi, açığa çıkma yüzeyi istek sayısıyla büyür. Tek bir kayıt, tek bir hatalı
yapılandırma, tek bir yanlış hedefe giden istek parolanın kendisini verir — oysa sonraki
derslerdeki şemalarda sızan şey, ömrü sınırlı bir türev olur.

İkincisi, **çıkış** diye bir işlem yoktur. Sunucuda silinecek bir kayıt bulunmadığı için
"bu istemcinin erişimini şimdi kes" isteğinin karşılığı yoktur; tek yol parolayı
değiştirmektir. Parola değişince o parolayı kullanan bütün istemciler aynı anda düşer.

Üçüncüsü, şema hesap ile istemciyi ayırmaz. Aynı hesabı üç raf terminali kullanıyorsa
sunucunun gördüğü tek bir kullanıcı adıdır; hangi terminalin ne yaptığı, hangisinin erişiminin
kesileceği ayırt edilemez. Ayrım isteniyorsa her istemciye ayrı hesap açmak gerekir ve bu,
hesap sayısını istemci sayısına bağlar.

## Şemanın Yeterli Olduğu Yer

Bu sınırlar şemayı kullanılamaz yapmaz; kullanım alanını daraltır. Temel şema, aşağıdaki
koşulların **hepsi** sağlandığında yeterlidir:

- Bağlantı TLS ile korunuyordur ve şifresiz bağlantıda uç hiç açılmaz.
- Kimlik bilgisi bir insanın parolası değil, tek bir istemciye ayrılmış rastgele üretilmiş bir
  değerdir; böylece iptal, o istemciyi düşürür ve başkasını etkilemez.
- Kimlik bilgisi sunucuda doğrulayıcı biçiminde saklanır; düz metin olarak değil.
- Günlükleme ve hata izleme kurulumunda `Authorization` başlığı gizleme listesindedir.
- İkinci etmen, oturum devamlılığı ve kullanıcıya görünen çıkış gibi gereksinimler yoktur.

Son koşul çoğu kullanıcıya dönük uygulamada sağlanmaz. Bir üye tarayıcıdan giriş yapıyorsa
oturum devamlılığı, çıkış düğmesi ve ikinci etmen beklenir; temel şema bu üçünü de vermez.
Buna karşılık iki servisin birbirini çağırdığı, TLS'in zorunlu olduğu ve kimlik bilgisinin
makineye ait olduğu bir kurulumda şema hem yeterli hem de az parçalı olduğu için tercih
edilebilir.

## Özet

- Temel şema iki adımdır: sunucunun 401 ile yolladığı davet ve istemcinin `Authorization`
  başlığıyla verdiği yanıt; `realm` bölgeyi, `charset` kodlamayı bildirir.
- Başlıkta taşınan değer base64 kodlamasıdır; çözmek için anahtar gerekmez, dolayısıyla
  gizlilik tümüyle taşıma katmanına bağlıdır.
- Kimlik bilgisi taşıyan başlıklar günlüğe olduğu gibi yazılırsa parola diske de yazılmış
  olur; gizleme listesi günlükleme kurulumunun parçası olmalıdır.
- Kimlik bilgisi karşılaştırması sabit zamanlı yapılır; erken çıkan karşılaştırma doğru önek
  uzunluğunu süreye yansıtır.
- Parola her istekte yeniden geçer; bunun sonucu büyüyen açığa çıkma yüzeyi, çıkış işleminin
  bulunmaması ve hesap ile istemcinin ayrılamamasıdır.
- Şema, TLS zorunluluğu ve istemciye özel rastgele kimlik bilgisiyle birlikte makineler arası
  çağrılarda yeterlidir; oturum, çıkış ve ikinci etmen gerektiren akışlarda yetersizdir.

## Sonraki Adım

Temel şemanın çözmediği sorunların ortak kaynağı, sunucunun istekler arasında hiçbir şey
hatırlamamasıdır. Sunucu bir kez doğrulayıp sonucu kendi tarafında saklarsa parola bir daha
ağa çıkmaz, çıkış işlemi anlam kazanır ve aynı hesabın farklı istemcileri ayrı ayrı
izlenebilir. Sonraki ders bu kaydı kurar: girişte sunucu tarafında bir oturum açılır, istemciye
yalnızca o kayda işaret eden bir kimlik verilir ve bu kimlik çerezle taşınır. Çerezin hangi
niteliklerle yollandığı ve oturum kimliğinin girişte niçin yenilendiği, kurulumun doğru olup
olmadığını belirleyen iki ayrıntıdır.
