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

# Belirteç Tabanlı Kimlik Doğrulama

Asılın isteğin taşıdığı imzalı değerin içine yazılması ve doğrulamanın yerel bir hesaba inmesi: donuk ile kendi kendine yeten belirteç ayrımı, taşıyıcı şeması, imzanın neyi güvenceye aldığı ve çıkışın anında etkili olmamasının ölçülmesi.

Oturum deposu bir bedel taşıyordu: her istek deponun okunmasını gerektiriyor, uygulama yatay
ölçeklendiğinde depo bütün süreçlerin ulaşabildiği bir yerde durmak zorunda kalıyordu.
Bedelin kaynağı, isteğin taşıdığı değerin **anlamsız** olmasıydı; kimliği anlama işi hep
sunucudaydı.

Bu ders bunun tersini kurar. Asılın kim olduğu ve neye yetkili olduğu, isteğin taşıdığı
değerin içine yazılır; değerin değiştirilmediğinden emin olmak için de imzalanır. Sunucu
doğrulama sırasında hiçbir kayda bakmaz, yalnız imzayı hesaplar. Kazanç ölçülebilir olduğu
gibi kayıp da ölçülebilir: kayıt yoksa çıkış neyi siler?

## Belirtecin İki Türü

**Donuk belirteç** (opaque token), taşıdığı değerin kendisi hiçbir anlam içermeyen bir
dizgedir. Önceki dersteki oturum kimliği bu türdendir: değer bir kayda işaret eder ve o kayıt
olmadan hiçbir şey söylemez. Belirteci alan bir servis, onu üreten servise sorarak — buna
**belirteç incelemesi** (token introspection) denir — geçerli olup olmadığını ve arkasındaki
asılı öğrenir.

**Kendi kendine yeten belirteç** (self-contained token), asıl hakkındaki bilgiyi kendi
içinde taşır ve bir imzayla korunur. Doğrulayan taraf imzayı hesaplayabiliyorsa hiçbir yere
sormaz. Bu türün kazancı doğrulamanın yerel olmasıdır: ödünç servisi, kataloğu ve ceza
servisi aynı belirteci ayrı ayrı doğrulayabilir, aralarında paylaşılan bir oturum deposu
gerekmez.

Ayrım bir ödünleşimdir. Donuk belirteçte geçerlilik kararı üretenin elindedir ve her an
değiştirilebilir; karşılığında her doğrulama bir sorgu ister. Kendi kendine yeten belirteçte
doğrulama ucuzdur; karşılığında karar, belirteç üretildiği anda dondurulmuştur.

## İmzalı Belirtecin Üretimi ve Doğrulanması

Aşağıdaki sunucu, gövdeyi base64url ile kodlayıp HMAC-SHA256 imzasını yanına ekler. Bu biçim
bu derse özgüdür; sonraki ders aynı işi yapan standart biçimi kurar.

```js
// belirtec.mjs — kendi kendine yeten, imzali belirtecle durumsuz kimlik dogrulama
// Kullanim: node belirtec.mjs <port> [iptal]
//   iptal: cikista belirtec kimligi iptal listesine yazilir
// Ortam: OMUR_MS (belirtec omru, ontanimli 6000)
import { createServer } from "node:http";
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";

const PORT = Number(process.argv[2] ?? 8531);
const IPTAL_LISTESI_VAR = process.argv.includes("iptal");
const OMUR_MS = Number(process.env.OMUR_MS ?? 6000);

const ANAHTAR = randomBytes(32);                    // imzalama anahtari; sunucuda kalir
const HESAPLAR = new Map([["deniz.aral", { parola: "üç-kitap-1876", kod: "U-1001", rol: "uye" }]]);

// Iptal listesi: belirtec kimligi -> ne zamana kadar tutulacagi
const IPTAL = new Map();
let dogrulamaSayisi = 0, depoOkumaSayisi = 0;

const b64 = (v) => Buffer.from(v).toString("base64url");
const imzala = (govde) => createHmac("sha256", ANAHTAR).update(govde).digest("base64url");

const uret = (asil) => {
  const govde = b64(JSON.stringify({ ...asil, jti: randomBytes(9).toString("base64url"), exp: Date.now() + OMUR_MS }));
  return `${govde}.${imzala(govde)}`;
};

const dogrula = (belirtec) => {
  dogrulamaSayisi++;
  const [govde, imza] = (belirtec ?? "").split(".");
  if (!govde || !imza) return { hata: "bicim" };

  const beklenen = Buffer.from(imzala(govde), "utf8");
  const gelen = Buffer.from(imza, "utf8");
  if (beklenen.length !== gelen.length || !timingSafeEqual(beklenen, gelen)) return { hata: "imza" };

  const icerik = JSON.parse(Buffer.from(govde, "base64url").toString("utf8"));
  if (Date.now() >= icerik.exp) return { hata: "sure" };

  if (IPTAL_LISTESI_VAR) {
    depoOkumaSayisi++;                              // durumsuzluktan vazgecilen tek nokta
    if (IPTAL.has(icerik.jti)) return { hata: "iptal" };
  }
  return { icerik };
};

const govdeOku = (istek) =>
  new Promise((coz) => { let v = ""; istek.on("data", (p) => (v += p)); istek.on("end", () => coz(v)); });

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const yol = istek.url.split("?")[0];
  const json = (kod, govde, baslik = {}) => {
    yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8", ...baslik });
    yanit.end(JSON.stringify(govde));
  };

  if (istek.method === "POST" && yol === "/giris") {
    const { kullanici, parola } = JSON.parse((await govdeOku(istek)) || "{}");
    const hesap = HESAPLAR.get(kullanici ?? "");
    if (!hesap || parola !== hesap.parola) return json(401, { hata: "kimlik dogrulanamadi" });
    return json(200, { belirtec: uret({ kod: hesap.kod, rol: hesap.rol }) });
  }

  const tasiyici = (istek.headers.authorization ?? "").startsWith("Bearer ")
    ? istek.headers.authorization.slice(7) : null;

  if (istek.method === "POST" && yol === "/cikis") {
    const sonuc = dogrula(tasiyici);
    if (sonuc.icerik && IPTAL_LISTESI_VAR) IPTAL.set(sonuc.icerik.jti, sonuc.icerik.exp);
    return json(200, { cikildi: true, iptalListesiVar: IPTAL_LISTESI_VAR });
  }

  if (yol === "/ben") {
    if (!tasiyici) {
      return json(401, { hata: "belirtec yok" }, { "www-authenticate": 'Bearer realm="odunc"' });
    }
    const sonuc = dogrula(tasiyici);
    if (sonuc.hata) {
      return json(401, { hata: sonuc.hata },
        { "www-authenticate": `Bearer realm="odunc", error="invalid_token", error_description="${sonuc.hata}"` });
    }
    return json(200, { kod: sonuc.icerik.kod, rol: sonuc.icerik.rol });
  }

  if (yol === "/tanilama") {
    // Suresi gecen kayitlar listeden dusulur: liste butun belirtecleri degil,
    // yalnizca omru dolmamis olanlari tutar.
    for (const [jti, exp] of IPTAL) if (Date.now() >= exp) IPTAL.delete(jti);
    return json(200, { dogrulamaSayisi, depoOkumaSayisi, iptalListesiUzunlugu: IPTAL.size });
  }
  json(404, { hata: "kaynak yok" });
}).listen(PORT, "127.0.0.1", () => console.log(`belirtec 127.0.0.1:${PORT} iptal=${IPTAL_LISTESI_VAR}`));
```

Belirteç, `Authorization` başlığında **taşıyıcı şemasıyla** (Bearer scheme) yollanır. Şemanın
adı anlamlıdır: belirteci taşıyan herkes onun sahibidir. Başlığı ele geçiren tarafın ayrıca
bir parolaya ihtiyacı yoktur; bu, temel şemadaki durumla aynı taşıma gereksinimini doğurur
ve şifresiz bağlantıyı yine dışarıda bırakır.

Reddetme yanıtı, sorunun ne olduğunu `WWW-Authenticate` başlığında bildirir.

```bash
OMUR_MS=60000 node belirtec.mjs 8533 > /dev/null & SUNUCU=$!
sleep 1
curl -s -D - -o /dev/null -H "Authorization: Bearer bozuk.deger" http://127.0.0.1:8533/ben | grep -i "^www-authenticate"
kill $SUNUCU
```

```
www-authenticate: Bearer realm="odunc", error="invalid_token", error_description="imza"
```

## Gövdenin Okunabilirliği

İmza, gövdenin sunucudan çıktığı hâlde olduğunu güvenceye alır. Gövdeyi **gizlemez**. Kodlama
base64url'dir ve önceki derste görüldüğü gibi çözmek için anahtar gerekmez.

```js
// icerik.mjs — belirtecin tasidigi govdenin okunabilirligi ve imzanin ne dogruladigi
// Kullanim: OMUR_MS=60000 node belirtec.mjs 8533 &  calisirken  node icerik.mjs 8533
const PORT = Number(process.argv[2] ?? 8533);
const TABAN = `http://127.0.0.1:${PORT}`;

const giris = await fetch(`${TABAN}/giris`, {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ kullanici: "deniz.aral", parola: "üç-kitap-1876" }),
});
const { belirtec } = await giris.json();
const [govde, imza] = belirtec.split(".");

console.log("gövde (base64url):", govde);
console.log("imza  (base64url):", imza);
console.log("gövdenin çözülmüş hâli:", Buffer.from(govde, "base64url").toString("utf8"));

const sor = async (deger) =>
  (await fetch(`${TABAN}/ben`, { headers: { authorization: `Bearer ${deger}` } })).status;

// Govde degistirilir, imza oldugu gibi birakilir: rolu "personel" yapan bir govde
const degisik = Buffer.from(JSON.stringify({ kod: "U-1001", rol: "personel", jti: "x", exp: Date.now() + 60000 }))
  .toString("base64url");

console.log("olduğu gibi           :", await sor(belirtec));
console.log("gövdesi değiştirilmiş :", await sor(`${degisik}.${imza}`));
console.log("imzası düşürülmüş     :", await sor(govde));
```

```bash
OMUR_MS=60000 node belirtec.mjs 8533 > /dev/null & SUNUCU=$!
sleep 1
node icerik.mjs 8533
kill $SUNUCU
```

```
gövde (base64url): eyJrb2QiOiJVLTEwMDEiLCJyb2wiOiJ1eWUiLCJqdGkiOiJ2V0JleEFXcjNPbkQiLCJleHAiOjE3ODUyNzIxODIzNzJ9
imza  (base64url): rKXePLI8VZZtAaq1LXbveTx7Q-ncpUX7e4EgiZHlBSo
gövdenin çözülmüş hâli: {"kod":"U-1001","rol":"uye","jti":"vWBexAWr3OnD","exp":1785272182372}
olduğu gibi           : 200
gövdesi değiştirilmiş : 401
imzası düşürülmüş     : 401
```

Belirteç kimliği ve zaman damgası her çalıştırmada değişir. Üç satırın söylediği ayrı ayrı
okunmalıdır. Gövde okunabilir: içindeki her alan, belirteci eline geçiren herkese açıktır.
Bu yüzden gövdeye parola, kimlik numarası, dâhilî tablo anahtarı ya da başka bir sır
yazılmaz. Gövdenin değiştirilmesi ise sonuç vermez: rolü `personel` yapan bir gövdenin eski
imzayla eşleşme olasılığı yoktur ve sunucu 401 döner. İmzanın düşürülmesi de aynı kapıya
çıkar — imzasız gövde biçim denetiminden geçemez.

Kısacası imza **bütünlüğü** sağlar, **gizliliği** sağlamaz. İkisinin karıştırılması, gövdeye
sır yazan kurulumların kaynağıdır.

## İptal Sorunu

Doğrulama hiçbir kayda bakmadığı için, sunucunun "bu belirteç artık geçersiz" diyebileceği
bir yer yoktur. Çıkış isteği belirteci istemciden silebilir ama başka bir kopyası varsa o
kopya çalışmayı sürdürür. Süre ölçülebilir: belirteç ömrü 6 saniyeye çekilir, çıkıştan sonra
`/ben` sürekli sorulur ve ilk başarısızlığa kadar geçen zaman yazılır.

```js
// iptal-olcumu.mjs — cikistan sonra belirtecin kac saniye gecerli kaldigini olcer
// Kullanim: OMUR_MS=6000 node belirtec.mjs 8531 &        (iptal listesi yok)
//           OMUR_MS=6000 node belirtec.mjs 8532 iptal &  (iptal listesi var)
//           node iptal-olcumu.mjs 8531   /   node iptal-olcumu.mjs 8532
const PORT = Number(process.argv[2]);
const TABAN = `http://127.0.0.1:${PORT}`;

const giris = await fetch(`${TABAN}/giris`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ kullanici: "deniz.aral", parola: "üç-kitap-1876" }),
});
const { belirtec } = await giris.json();
const bas = { authorization: `Bearer ${belirtec}` };
const durum = async () => (await fetch(`${TABAN}/ben`, { headers: bas })).status;

console.log("çıkış öncesi /ben :", await durum());
await fetch(`${TABAN}/cikis`, { method: "POST", headers: bas });

const cikisAni = Date.now();
let gecerliKaldi = 0;
for (;;) {
  const kod = await durum();
  if (kod !== 200) break;
  gecerliKaldi = Date.now() - cikisAni;
  await new Promise((c) => setTimeout(c, 250));
}
console.log("çıkıştan sonra geçerli kaldığı süre:", (gecerliKaldi / 1000).toFixed(1), "sn");
console.log("tanılama:", await (await fetch(`${TABAN}/tanilama`)).json());
```

```bash
OMUR_MS=6000 node belirtec.mjs 8531 > /dev/null & LISTESIZ=$!
OMUR_MS=6000 node belirtec.mjs 8532 iptal > /dev/null & LISTELI=$!
sleep 1
echo "— iptal listesi yok —"
node iptal-olcumu.mjs 8531
echo "— iptal listesi var —"
node iptal-olcumu.mjs 8532
kill $LISTESIZ $LISTELI
```

```
— iptal listesi yok —
çıkış öncesi /ben : 200
çıkıştan sonra geçerli kaldığı süre: 5.9 sn
tanılama: { dogrulamaSayisi: 27, depoOkumaSayisi: 0, iptalListesiUzunlugu: 0 }
— iptal listesi var —
çıkış öncesi /ben : 200
çıkıştan sonra geçerli kaldığı süre: 0.0 sn
tanılama: { dogrulamaSayisi: 3, depoOkumaSayisi: 3, iptalListesiUzunlugu: 1 }
```

Ölçülen süreler yüzde birkaç oynar; belirleyici olan büyüklük mertebesidir. Listesiz
kurulumda çıkış isteğinin sunucu tarafındaki karşılığı boştur: belirteç, ömrünün sonuna
kadar çalışır. Altı saniyelik ömürde bu altı saniyedir; sekiz saatlik ömürde sekiz saattir.
Önceki dersin oturum deposunda aynı ölçüm sıfır veriyordu, çünkü karar bir kaydın
silinmesiydi.

Aynı çıktı, kazancın nereden geldiğini de gösteriyor. Listesiz kurulumda `depoOkumaSayisi`
sıfırdır: yirmi yedi doğrulama yapıldı ve hiçbiri hiçbir yere sormadı. Listeli kurulumda
`iptalListesiUzunlugu` bire çıkmıştır; çıkışta yazılan tek kayıt, ömrü dolana kadar orada
durur.

## İptal Listesinin Bedeli

İkinci kurulum, çıkışta belirteç kimliğini bir **iptal listesine** yazar ve her doğrulamada
o listeye bakar. Ölçüm sıfır saniye verir: karar anında etkilidir. Ama `depoOkumaSayisi`
artık doğrulama sayısına eşittir — durumsuzluk bu noktada bırakılmıştır.

Yine de iki kurulum arasında nicel bir fark kalır. Oturum deposu **açık olan bütün
oturumları** tutar ve kayıt kullanıcı çıkana ya da ömür dolana kadar durur. İptal listesi
yalnızca **iptal edilmiş ve henüz ömrü dolmamış** belirteçleri tutar; ömrü dolan kayıt
listeden düşer, çünkü zaten süre denetimi onu reddedecektir. Belirteç ömrü kısaldıkça bu
liste küçülür.

Buradan pratik kural çıkar: durumsuz belirteçte iptalin çözümü listeyi büyütmek değil, ömrü
kısaltmaktır. Ömür kısaldığında iptal penceresi de kısalır. Ama kısa ömür, kullanıcıyı
dakikada bir yeniden giriş yapmaya zorlar; bu ikilemin çözümü ayrı bir derstir.

## Özet

- Donuk belirteç bir kayda işaret eder ve doğrulaması sorgu ister; kendi kendine yeten
  belirteç bilgiyi kendi içinde taşır ve doğrulaması yereldir.
- Kendi kendine yeten belirteç imzalanır; imza gövdenin değişmediğini güvenceye alır ama
  gövdeyi gizlemez, bu yüzden gövdeye sır yazılmaz.
- Belirteç taşıyıcı şemasıyla yollanır; taşıyanın sahip sayılması, şifresiz bağlantıyı yine
  dışarıda bırakır ve reddetme nedeni `WWW-Authenticate` başlığında bildirilir.
- Sunucuda kayıt olmadığı için çıkış anında etkili olmaz: ölçümde belirteç, ömrü bitene
  kadar çalışmayı sürdürdü.
- İptal listesi kararı anında etkili kılar ama her doğrulamayı bir okumaya bağlar; liste
  yalnız ömrü dolmamış iptalleri tuttuğu için oturum deposundan küçüktür.
- Durumsuz kurulumda iptal penceresini belirleyen değişken belirteç ömrüdür; kısa ömür
  pencereyi daraltır ve asıl çözüm buradadır.

## Sonraki Adım

Bu derste kullanılan belirteç biçimi bu derse özgüydü: gövde, nokta, imza. Aynı işi yapan
yaygın bir standart vardır ve alanların adlarını, imza algoritmasının nasıl bildirileceğini,
sürenin hangi birimle yazılacağını sabitler. Standart olmak, doğrulamanın da standartlaşması
demektir — ve doğrulayıcının hangi adımları atlamaması gerektiği, biçimin kendisinden daha
önemlidir. Sonraki ders bu biçimi ayrıntısıyla kurar, imzayı elle üretip doğrular ve
doğrulayıcının belirtecin kendi söylediğine güvendiği noktada niçin çöktüğünü gösterir.
