---
title: 'JSON Web Belirteçleri'
source: 'https://academia.sh/tr/kurslar/kimlik-ve-yetki/json-web-belirtecleri'
course: 'Kimlik Doğrulama ve Yetkilendirme'
language: tr
updated: '2026-08-17T18:06:48+00:00'
license: 'CC BY-SA 4.0'
---

# JSON Web Belirteçleri

İmzalı belirtecin standart biçimi: üç parçalı sıkıştırılmış serileştirme, kayıtlı iddiaların anlamı, doğrulayıcının atlamaması gereken adımlar, algoritmanın belirtece değil yapılandırmaya sorulması, saat kayması payı ve imzalama anahtarının uzunluğu.

Önceki dersin belirteç biçimi o derse özgüydü: gövde, nokta, imza. Aynı işi yapan yaygın bir
standart vardır — **JSON web belirteci** (JSON Web Token, JWT) — ve alanların adlarını, imza
algoritmasının nasıl bildirileceğini, sürenin hangi birimle yazılacağını sabitler. Biçimin
standartlaşmasının asıl sonucu doğrulamanın standartlaşmasıdır: bir belirteci kabul etmeden
önce hangi soruların sorulacağı da tanımlıdır.

Bu ders belirteci elle üretir ve elle doğrular. İki doğrulayıcı yazılır: biri beklediği
algoritmayı kendi yapılandırmasından alır, diğeri belirtecin başlığında yazana bakar. İkisinin
aynı belirteçler için verdiği kararlar yan yana konduğunda, doğrulamanın hangi adımının
vazgeçilmez olduğu ölçülebilir hâle gelir.

## Üç Parça

Belirteç, nokta ile ayrılmış üç base64url parçasından oluşur: başlık, gövde ve imza. İmza,
ilk iki parçanın **kodlanmış hâlleri** üzerine atılır; çözülmüş JSON üzerine değil. Bu ayrım
önemlidir, çünkü aynı JSON nesnesinin farklı boşluk düzenleriyle farklı kodlamaları olur ve
imza belirtecin taşındığı baytları bağlar.

Bu üç parçalı gösterime **sıkıştırılmış serileştirme** (compact serialization) denir; imzalı
veriyi tanımlayan JWS standardının başlıkta, sorgu dizgesinde ve gövdede taşınabilecek
biçimidir.

```js
// jwt.mjs — JWS sikistirilmis serilestirmesinin uretimi ve iki ayri dogrulayici
import { createHmac, timingSafeEqual } from "node:crypto";

export const b64 = (v) => Buffer.from(v).toString("base64url");
export const b64Coz = (v) => Buffer.from(v, "base64url").toString("utf8");

const HMAC = { HS256: "sha256", HS384: "sha384", HS512: "sha512" };

const imzala = (alg, veri, anahtar) =>
  alg === "none" ? "" : createHmac(HMAC[alg], anahtar).update(veri).digest("base64url");

// Belirtec uretimi: baslik ve govde base64url ile kodlanir, imza ikisinin uzerine atilir.
export const uret = ({ alg = "HS256", kid, iddialar, anahtar }) => {
  const baslik = b64(JSON.stringify(kid ? { alg, typ: "JWT", kid } : { alg, typ: "JWT" }));
  const govde = b64(JSON.stringify(iddialar));
  return `${baslik}.${govde}.${imzala(alg, `${baslik}.${govde}`, anahtar)}`;
};

const esitMi = (a, b) => {
  const x = Buffer.from(a, "utf8"), y = Buffer.from(b, "utf8");
  return x.length === y.length && timingSafeEqual(x, y);
};

// DOGRU DOGRULAYICI: beklenen algoritmayi cagiran sabitler, baslik yalniz kid icin okunur.
export const dogrula = (belirtec, { anahtar, beklenenAlg, yayinci, hedefKitle, kaymaSn = 0 }) => {
  const parcalar = (belirtec ?? "").split(".");
  if (parcalar.length !== 3) return { hata: "bicim" };
  const [baslikB64, govdeB64, imza] = parcalar;

  let baslik, iddialar;
  try {
    baslik = JSON.parse(b64Coz(baslikB64));
    iddialar = JSON.parse(b64Coz(govdeB64));
  } catch { return { hata: "cozulemedi" }; }

  // 1. Algoritma dogrulayicinin yapilandirmasindan gelir; belirtecin bildirdiginden degil.
  if (baslik.alg !== beklenenAlg) return { hata: "alg" };

  // 2. Imza, beklenen algoritmayla yeniden hesaplanir.
  if (!esitMi(imza, imzala(beklenenAlg, `${baslikB64}.${govdeB64}`, anahtar))) return { hata: "imza" };

  // 3. Zaman iddialari (saniye cinsinden), saat kaymasi payiyla.
  const simdi = Math.floor(Date.now() / 1000);
  if (typeof iddialar.exp !== "number") return { hata: "exp yok" };
  if (simdi >= iddialar.exp + kaymaSn) return { hata: "suresi gecmis" };
  if (typeof iddialar.nbf === "number" && simdi < iddialar.nbf - kaymaSn) return { hata: "henuz gecerli degil" };

  // 4. Yayinci ve hedef kitle.
  if (iddialar.iss !== yayinci) return { hata: "yayinci" };
  const kitle = Array.isArray(iddialar.aud) ? iddialar.aud : [iddialar.aud];
  if (!kitle.includes(hedefKitle)) return { hata: "hedef kitle" };

  return { iddialar };
};

// YANLIS DOGRULAYICI: algoritmayi belirtecin basligindan okur. Karsi ornek olarak durur.
export const dogrulaBasligaGuvenerek = (belirtec, { anahtar }) => {
  const [baslikB64, govdeB64, imza] = (belirtec ?? "").split(".");
  let baslik, iddialar;
  try {
    baslik = JSON.parse(b64Coz(baslikB64));
    iddialar = JSON.parse(b64Coz(govdeB64));
  } catch { return { hata: "cozulemedi" }; }
  if (!esitMi(imza ?? "", imzala(baslik.alg, `${baslikB64}.${govdeB64}`, anahtar))) return { hata: "imza" };
  return { iddialar };
};
```

Üretilen belirtecin parçaları doğrudan okunabilir.

```js
// yapi.mjs — belirtecin uc parcasi ve iddialarin okunmasi
// Kullanim: node yapi.mjs   (jwt.mjs ile ayni dizinde)
import { uret, b64Coz } from "./jwt.mjs";

const ANAHTAR = Buffer.from("bu-anahtar-yalniz-bu-derste-kullanilir-32b", "utf8");
const simdi = 1785000000;                     // ornek sabit zaman damgasi (saniye)

const belirtec = uret({
  alg: "HS256",
  kid: "2026-01",
  anahtar: ANAHTAR,
  iddialar: {
    iss: "https://kimlik.kutuphane.ornek",    // yayinci
    sub: "U-1001",                            // konu: belirtecin anlattigi asil
    aud: "odunc-servisi",                     // hedef kitle: belirteci kabul edecek servis
    iat: simdi,                               // uretim ani
    nbf: simdi,                               // bu andan once gecerli degil
    exp: simdi + 900,                         // 15 dakika
    jti: "b-000001",                          // belirtec kimligi
    rol: "uye",                               // servise ozgu iddia
  },
});

const [baslik, govde, imza] = belirtec.split(".");
console.log("belirteç uzunluğu :", belirtec.length, "karakter");
console.log("başlık  :", b64Coz(baslik));
console.log("gövde   :", b64Coz(govde));
console.log("imza    :", imza, `(${Buffer.from(imza, "base64url").length} bayt)`);
console.log("imzanın hesaplandığı veri:", `${baslik.slice(0, 12)}….${govde.slice(0, 12)}…`);
```

```
belirteç uzunluğu : 313 karakter
başlık  : {"alg":"HS256","typ":"JWT","kid":"2026-01"}
gövde   : {"iss":"https://kimlik.kutuphane.ornek","sub":"U-1001","aud":"odunc-servisi","iat":1785000000,"nbf":1785000000,"exp":1785000900,"jti":"b-000001","rol":"uye"}
imza    : TLNdRXO5CI9Y6Q-xpcfLsiWwBaKKzxVB3YIaJG070wU (32 bayt)
imzanın hesaplandığı veri: eyJhbGciOiJI….eyJpc3MiOiJo…
```

Belirteç 313 karakterdir ve her istekte bu uzunlukta bir başlık taşınır. Gövdeye eklenen her
iddia bu sayıyı büyütür; belirteç bir veri taşıyıcısı değil, bir kimlik kanıtıdır.

## Kayıtlı İddialar

Gövdedeki alanlara **iddia** (claim) denir. Bir bölümü standartta adlandırılmıştır ve anlamı
sabittir.

- `iss` — belirteci üreten yayıncı. Doğrulayıcı, beklediği yayıncıyı kendi yapılandırmasında
  tutar ve eşleşmeyeni reddeder.
- `sub` — belirtecin anlattığı asıl. Ödünç servisinde bu, üye ya da personel kodudur.
- `aud` — hedef kitle: belirteci kabul etmesi beklenen alıcı. Tek bir dizge ya da dizge
  dizisi olabilir.
- `exp` — geçerliliğin bittiği an; `nbf` — geçerliliğin başladığı an; `iat` — üretim anı. Üçü
  de 1970 başlangıçlı **saniye** cinsindendir. JavaScript'in milisaniye tabanlı damgasıyla
  karıştırılması, bin kat uzun ya da kısa ömürlü belirteç üretir.
- `jti` — belirteç kimliği. İptal listesi ve yeniden kullanım saptaması bu alana dayanır.

`aud` iddiası, doğrulamada en sık atlanan alandır ve atlanması en somut sonucu doğurur. Ödünç
servisi için üretilmiş bir belirteç, `aud` denetlenmediğinde ceza servisinde de geçer. İki
servis aynı yayıncıya güveniyorsa aralarındaki tek sınır bu alandır.

Standartta yeri olmayan iddialar da yazılabilir — yukarıdaki `rol` gibi. Bunların çakışmaması
için ad alanı ayırmak gerekir; ayrıca önceki derste kurulan kural burada da geçerlidir: gövde
okunabilir olduğu için sır yazılmaz.

## Sekiz Denemenin Kararı

Aşağıdaki dizge, sekiz farklı belirteci iki doğrulayıcıya sorar. Belirteçlerin hepsi bu
dosyanın kendi ürettiği deneme değerleridir.

```js
// dogrulama-dizgesi.mjs — sekiz deneme belirtecinin iki dogrulayicidan aldigi karar
// Kullanim: node dogrulama-dizgesi.mjs   (jwt.mjs ile ayni dizinde)
import { uret, dogrula, dogrulaBasligaGuvenerek, b64 } from "./jwt.mjs";

const ANAHTAR = Buffer.from("bu-anahtar-yalniz-bu-derste-kullanilir-32b", "utf8");
const BASKA_ANAHTAR = Buffer.from("bu-anahtar-baska-bir-servise-aittir-32bayt", "utf8");
const simdi = Math.floor(Date.now() / 1000);

const taban = {
  iss: "https://kimlik.kutuphane.ornek", sub: "U-1001", aud: "odunc-servisi",
  iat: simdi, nbf: simdi, exp: simdi + 900, jti: "b-000001", rol: "uye",
};
const yap = (deg, alg = "HS256", anahtar = ANAHTAR) => uret({ alg, anahtar, iddialar: { ...taban, ...deg } });

// Govdesi degistirilmis belirtec: imza oldugu gibi birakilir.
const saglam = yap({});
const [b, , i] = saglam.split(".");
const govdesiDegisik = `${b}.${b64(JSON.stringify({ ...taban, rol: "personel" }))}.${i}`;

const DENEMELER = [
  ["geçerli", saglam],
  ["süresi geçmiş", yap({ exp: simdi - 30 })],
  ["henüz geçerli değil", yap({ nbf: simdi + 60 })],
  ["hedef kitle başka servis", yap({ aud: "ceza-servisi" })],
  ["yayıncı başka", yap({ iss: "https://baska.ornek" })],
  ["gövdesi değiştirilmiş", govdesiDegisik],
  ["başlıkta alg: none", yap({}, "none", ANAHTAR)],
  ["başka anahtarla imzalı", yap({}, "HS256", BASKA_ANAHTAR)],
];

const AYAR = {
  anahtar: ANAHTAR, beklenenAlg: "HS256",
  yayinci: "https://kimlik.kutuphane.ornek", hedefKitle: "odunc-servisi",
};

const yaz = (sonuc) => (sonuc.hata ? `ret (${sonuc.hata})` : `kabul (rol=${sonuc.iddialar.rol})`);

console.log("deneme".padEnd(26) + "algoritmayı sabitleyen".padEnd(26) + "başlığa güvenen");
for (const [ad, belirtec] of DENEMELER) {
  console.log(ad.padEnd(26) + yaz(dogrula(belirtec, AYAR)).padEnd(26) + yaz(dogrulaBasligaGuvenerek(belirtec, AYAR)));
}
```

```
deneme                    algoritmayı sabitleyen    başlığa güvenen
geçerli                   kabul (rol=uye)           kabul (rol=uye)
süresi geçmiş             ret (suresi gecmis)       kabul (rol=uye)
henüz geçerli değil       ret (henuz gecerli degil) kabul (rol=uye)
hedef kitle başka servis  ret (hedef kitle)         kabul (rol=uye)
yayıncı başka             ret (yayinci)             kabul (rol=uye)
gövdesi değiştirilmiş     ret (imza)                ret (imza)
başlıkta alg: none        ret (alg)                 kabul (rol=uye)
başka anahtarla imzalı    ret (imza)                ret (imza)
```

Sağdaki sütun yalnız imzayı denetleyen bir doğrulayıcıyı gösteriyor ve sekiz denemenin
altısını kabul ediyor. Süresi geçmiş, henüz geçerli olmayan, başka servis için üretilmiş ve
başka yayıncıdan gelen belirteçler geçiyor; çünkü imza doğrulaması bu soruların hiçbirini
sormaz. İmza "bu belirteç değiştirilmedi" der; "bu belirteç sana, şimdi ve bu yayıncıdan
geldi" demez. Doğrulama, imzayla **başlar** ama imzayla bitmez.

Yedinci satır ayrı bir kural üretir. `alg` alanı belirtecin başlığındadır, yani belirteci
üretenin yazdığı bir değerdir. Doğrulayıcı bu değeri okuyup ona göre davranırsa, hangi
denetimin uygulanacağına belirtecin kendisi karar vermiş olur. Sağdaki doğrulayıcı `none`
bildiren belirteci kabul ediyor: imza boştur, hesaplanan imza da boştur, karşılaştırma
geçer. Soldaki doğrulayıcı aynı belirteci daha ilk adımda reddeder, çünkü beklediği algoritma
yapılandırmasında yazılıdır.

Kural şudur: doğrulayıcı beklediği algoritmayı **kendi yapılandırmasından** alır ve başlıktaki
`alg` alanını yalnız uyuşmazlığı saptamak için okur. Başlığın `kid` alanı bundan ayrıdır:
`kid` hangi anahtarın kullanılacağını söyler ve doğrulayıcının **tanıdığı** anahtarlar
arasından seçim yapar; tanımadığı bir anahtara yönlendiremez.

## Saat Kayması Payı

Zaman iddiaları iki ayrı makinenin saatine bağlıdır ve saatler tam olarak eşitlenmez. Katı
bir doğrulayıcı, birkaç saniye ileri giden bir üreticinin yeni ürettiği belirteci "henüz
geçerli değil" diye reddedebilir. Bu yüzden doğrulayıcılar küçük bir **saat kayması payı**
tanır.

```js
// kayma.mjs — saat kaymasi payinin dogrulama kararina etkisi
// Kullanim: node kayma.mjs   (jwt.mjs ile ayni dizinde)
import { uret, dogrula } from "./jwt.mjs";

const ANAHTAR = Buffer.from("bu-anahtar-yalniz-bu-derste-kullanilir-32b", "utf8");
const simdi = Math.floor(Date.now() / 1000);
const AYAR = {
  anahtar: ANAHTAR, beklenenAlg: "HS256",
  yayinci: "https://kimlik.kutuphane.ornek", hedefKitle: "odunc-servisi",
};
const iddialar = (deg) => ({
  iss: "https://kimlik.kutuphane.ornek", sub: "U-1001", aud: "odunc-servisi",
  iat: simdi, exp: simdi + 900, ...deg,
});

const sor = (belirtec, kaymaSn) => {
  const s = dogrula(belirtec, { ...AYAR, kaymaSn });
  return s.hata ? `ret (${s.hata})` : "kabul";
};

const suresiGecmis = uret({ anahtar: ANAHTAR, iddialar: iddialar({ exp: simdi - 2 }) });
const henuzGecerliDegil = uret({ anahtar: ANAHTAR, iddialar: iddialar({ nbf: simdi + 2 }) });

console.log("pay".padEnd(8) + "2 sn önce süresi dolmuş".padEnd(28) + "2 sn sonra geçerli olacak");
for (const kayma of [0, 5, 60]) {
  console.log(`${kayma} sn`.padEnd(8) + sor(suresiGecmis, kayma).padEnd(28) + sor(henuzGecerliDegil, kayma));
}
```

```
pay     2 sn önce süresi dolmuş     2 sn sonra geçerli olacak
0 sn    ret (suresi gecmis)         ret (henuz gecerli degil)
5 sn    kabul                       kabul
60 sn   kabul                       kabul
```

Pay iki yönde de çalışır ve belirtecin fiili ömrünü uzatır. Beş saniyelik bir pay, on beş
dakikalık belirteci on beş dakika beş saniye geçerli kılar; altmış saniyelik pay, kısa ömürlü
belirteç kurmanın anlamını aşındırmaya başlar. Ölçü, saat eşitleme hatasının büyüklüğüdür:
birkaç saniye yeterlidir, dakikalar bir yapılandırma hatasını örtmek için kullanılıyor
demektir.

## Anahtarın Uzunluğu ve Sahibi

HMAC ile imzalamada anahtar bir sırdır ve onu bilen herkes geçerli belirteç üretebilir. Bu
yüzden imzalayan ile doğrulayan aynı sırrı paylaşır. Anahtarın uzunluğu doğrulamanın
çalışmasını etkilemez; yalnızca dayanıklılığı belirler.

```js
// anahtar.mjs — HMAC anahtarinin uzunlugunun dogrulamayi engellemedigi, ama dayanikligi belirledigi
import { createHmac } from "node:crypto";

for (const anahtar of ["1234", "kutuphane", "bu-anahtar-yalniz-bu-derste-kullanilir-32b"]) {
  const bayt = Buffer.from(anahtar, "utf8").length;
  const imza = createHmac("sha256", anahtar).update("baslik.govde").digest("base64url");
  console.log(`${bayt.toString().padStart(2)} bayt anahtar → imza ${imza.slice(0, 16)}… (uzunluk ${Buffer.from(imza, "base64url").length} bayt)`);
}
```

```
 4 bayt anahtar → imza ATOF6UVSe9E4W95T… (uzunluk 32 bayt)
 9 bayt anahtar → imza -pmpkxXgviiFGFpW… (uzunluk 32 bayt)
42 bayt anahtar → imza GK0vNqB99PV6Lz-9… (uzunluk 32 bayt)
```

Üç imza da 32 bayttır ve hiçbiri anahtarın zayıf olduğunu belli etmez. Dört baytlık bir
anahtarla üretilen belirteç, doğrulamadan sorunsuz geçer; sorun doğrulamada değil, anahtarın
denenerek bulunabilmesindedir. Bu yüzden anahtar uzunluğu bir çalışma zamanı denetimi değil,
bir yapılandırma kuralıdır: HMAC anahtarı en az özet uzunluğu kadar — SHA-256 için 32 bayt —
rastgele veriden üretilir. Sözlükte geçen bir parola, uzunluğu tutsa bile bu koşulu
sağlamaz.

Paylaşılan sırrın ikinci sonucu ölçek sorunudur. Ödünç, katalog ve ceza servisleri aynı
belirteci doğruluyorsa üçü de aynı sırrı bilir; üçünden birinin sızması, belirteç üretme
gücünün sızması demektir. Asimetrik imzada bu ayrılır: yayıncı özel anahtarla imzalar,
doğrulayanlar yalnız açık anahtarı bilir ve açık anahtarla belirteç üretilemez. Başlıktaki
`kid` alanı da burada anlamlanır — yayıncı birden çok anahtar kullanabilir ve doğrulayan,
belirtecin hangi anahtarla imzalandığını bu alandan öğrenip kendi tanıdığı anahtarlar
arasından seçer.

## Özet

- JSON web belirteci nokta ile ayrılmış üç base64url parçadır; imza, başlık ve gövdenin
  kodlanmış hâlleri üzerine atılır.
- Kayıtlı iddialar sabit anlamlıdır: `iss` yayıncıyı, `sub` asılı, `aud` hedef kitleyi,
  `exp`/`nbf`/`iat` saniye cinsinden zamanları, `jti` belirteç kimliğini taşır.
- İmza doğrulaması yalnız değiştirilmemişliği söyler; süre, yayıncı ve hedef kitle
  denetimleri ayrı adımlardır ve atlandıklarında başka servis için üretilmiş ya da süresi
  geçmiş belirteçler kabul edilir.
- Doğrulayıcı beklediği algoritmayı kendi yapılandırmasından alır; `alg` alanını okuyup ona
  göre davranan doğrulayıcı, hangi denetimin uygulanacağını belirtece bırakmış olur.
- Saat kayması payı belirtecin fiili ömrünü uzatır; birkaç saniye saat eşitleme hatası
  içindir, dakikalar bir yapılandırma sorununu örter.
- HMAC anahtarının uzunluğu doğrulamayı etkilemez, dayanıklılığı belirler; paylaşılan sır
  bütün doğrulayıcılara belirteç üretme gücü verir, asimetrik imza bu gücü yayıncıda tutar.

## Sonraki Adım

Önceki dersin ölçümü, durumsuz belirteçte iptal penceresini belirleyen değişkenin ömür
olduğunu göstermişti. Bu ders ömrü `exp` alanına bağladı ama ikilem duruyor: kısa ömür iptal
penceresini daraltır, buna karşılık kullanıcıyı sık sık yeniden giriş yapmaya zorlar. Çözüm,
tek bir belirteç yerine iki belirteç kullanmaktır — biri kısa ömürlü ve her istekte giden,
diğeri uzun ömürlü ve yalnız yenileme için kullanılan. Sonraki ders bu ikiliyi kurar, uzun
ömürlü belirtecin her kullanımda değiştirilmesinin niçin gerektiğini ve aynı belirtecin ikinci
kez kullanılmasının nasıl saptanabildiğini çalıştırarak gösterir.
