---
title: 'OAuth 2 Akışları'
source: 'https://academia.sh/tr/kurslar/kimlik-ve-yetki/oauth-2-akislari'
course: 'Kimlik Doğrulama ve Yetkilendirme'
language: tr
updated: '2026-08-17T18:06:48+00:00'
license: 'CC BY-SA 4.0'
---

# OAuth 2 Akışları

Yetki devrinin dört rolle kurulması: yetkilendirme sunucusu, kaynak sunucusu ve istemci ile uçtan uca çalıştırılan yetki kodu akışı, PKCE doğrulayıcısı ve state parametresinin kuralları, kodun tek kullanımlığı ile istemci kimlik bilgisi ve aygıt akışları.

Buraya kadar kimlik doğrulamanın bütün adımları tek tarafta geçti: kullanıcı parolasını ödünç
servisine verdi, ödünç servisi belirteci üretti ve yine kendisi doğruladı. Bir üyenin ödünç
geçmişini üçüncü taraf bir okuma uygulamasına açmak istediği durumda bu düzen çalışmaz —
uygulamaya parolanın verilmesi gerekirdi. Parolayı alan uygulama ise ödünç geçmişini okumakla
kalmaz, üyenin yapabildiği her şeyi yapabilir ve erişimi ancak parola değiştirilerek kesilir.

**OAuth 2**, bu sorunu yetkiyi devrederek çözer. Kaynak sahibi kimlik bilgisini yalnız kendi
yetkilendirme sunucusuna verir; uygulamaya çıkan şey, kapsamı ve ömrü sınırlı bir belirteçtir.

## Dört Rol

**Kaynak sahibi** (resource owner) yetkiyi veren taraftır — burada üye U-1001. **İstemci**
(client) yetkiyi isteyen uygulamadır. **Yetkilendirme sunucusu** (authorization server)
kaynak sahibini doğrular, onayını alır ve belirteç üretir. **Kaynak sunucusu** (resource
server) belirteci doğrulayıp korunan veriyi verir.

Yetki kodu akışında istemci belirteci doğrudan almaz. Kaynak sahibi yetkilendirme sunucusuna
yönlendirilir, oradan istemciye kısa ömürlü bir **yetki kodu** (authorization code) döner ve
istemci bu kodu, tarayıcıdan geçmeyen ayrı bir istekle belirteçle değiştirir. Belirtecin
tarayıcı adres çubuğunda hiç görünmemesinin nedeni budur.

## Yetkilendirme Sunucusu

```js
// yetkilendirme-sunucusu.mjs — yetki kodu (PKCE), istemci kimlik bilgisi ve aygit akislari
// Kullanim: node yetkilendirme-sunucusu.mjs [port]     (ontanimli 8551)
import { createServer } from "node:http";
import { createHmac, createHash, randomBytes, timingSafeEqual } from "node:crypto";

const PORT = Number(process.argv[2] ?? 8551);
const YAYINCI = `http://127.0.0.1:${PORT}`;
const ANAHTAR = Buffer.from("kimlik-saglayici-imza-anahtari-32bayt!!!", "utf8");

// "acik" istemci sir saklayamaz (tarayici/mobil), "gizli" istemci saklayabilir.
const ISTEMCILER = new Map([
  ["okuma-uygulamasi", { tur: "acik", yonlendirme: ["http://127.0.0.1:8553/geri"], kapsam: ["odunc:oku"] }],
  ["raf-terminali", { tur: "gizli", sir: "raf-terminali-sirri-1", kapsam: ["odunc:oku", "odunc:yaz"] }],
]);
const KODLAR = new Map(), AYGITLAR = new Map();

const s256 = (v) => createHash("sha256").update(v).digest("base64url");
const esitMi = (a, b) => {
  const x = Buffer.from(a, "utf8"), y = Buffer.from(b, "utf8");
  return x.length === y.length && timingSafeEqual(x, y);
};
const erisimUret = (sub, istemci, kapsam) => {
  const g = Buffer.from(JSON.stringify({ iss: YAYINCI, sub, aud: "odunc-servisi", client_id: istemci,
    scope: kapsam.join(" "), exp: Math.floor(Date.now() / 1000) + 600 })).toString("base64url");
  return `${g}.${createHmac("sha256", ANAHTAR).update(g).digest("base64url")}`;
};
const formOku = (istek) => new Promise((coz) => {
  let v = ""; istek.on("data", (p) => (v += p));
  istek.on("end", () => coz(Object.fromEntries(new URLSearchParams(v))));
});

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const adres = new URL(istek.url, YAYINCI), s = adres.searchParams;
  const json = (kod, govde) => {
    yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
    yanit.end(JSON.stringify(govde));
  };

  if (istek.method === "GET" && adres.pathname === "/yetkilendir") {
    const istemci = ISTEMCILER.get(s.get("client_id") ?? ""), yon = s.get("redirect_uri") ?? "";
    // Istemci ya da yonlendirme adresi taninmiyorsa YONLENDIRME YAPILMAZ; hata burada durur.
    if (!istemci || !(istemci.yonlendirme ?? []).includes(yon)) {
      return json(400, { error: "invalid_request", detay: "istemci ya da yonlendirme adresi taninmiyor" });
    }
    const hataylaDon = (kod) => {
      const u = new URL(yon);
      u.searchParams.set("error", kod);
      if (s.get("state")) u.searchParams.set("state", s.get("state"));
      yanit.writeHead(302, { location: u.toString() }); yanit.end();
    };
    if (s.get("response_type") !== "code" || !s.get("state")) return hataylaDon("invalid_request");
    if (!s.get("code_challenge") || s.get("code_challenge_method") !== "S256") return hataylaDon("invalid_request");
    const istenen = (s.get("scope") ?? "").split(" ").filter(Boolean);
    if (!istenen.length || !istenen.every((k) => istemci.kapsam.includes(k))) return hataylaDon("invalid_scope");

    // Bu noktada kaynak sahibi kimligini dogrulamis ve onay ekranini gecmis sayiliyor.
    const kod = randomBytes(24).toString("base64url");
    KODLAR.set(kod, { client_id: s.get("client_id"), redirect_uri: yon, meydan: s.get("code_challenge"),
      kapsam: istenen, sub: "U-1001", exp: Date.now() + 60_000, kullanildi: false });
    const u = new URL(yon);
    u.searchParams.set("code", kod); u.searchParams.set("state", s.get("state"));
    yanit.writeHead(302, { location: u.toString() });
    return yanit.end();
  }

  if (istek.method === "POST" && adres.pathname === "/aygit/kod") {
    const g = await formOku(istek);
    const aygitKodu = randomBytes(18).toString("base64url"), kullaniciKodu = randomBytes(3).toString("hex").toUpperCase();
    AYGITLAR.set(aygitKodu, { client_id: g.client_id, kullaniciKodu, onay: false, kapsam: (g.scope ?? "").split(" ") });
    return json(200, { device_code: aygitKodu, user_code: kullaniciKodu,
      verification_uri: `${YAYINCI}/aygit`, expires_in: 600, interval: 1 });
  }
  if (istek.method === "POST" && adres.pathname === "/aygit/onay") {
    const g = await formOku(istek);
    for (const [, k] of AYGITLAR) if (k.kullaniciKodu === g.user_code) k.onay = true;
    return json(200, { onaylandi: true });
  }

  if (istek.method === "POST" && adres.pathname === "/belirtec") {
    const g = await formOku(istek);

    if (g.grant_type === "authorization_code") {
      const k = KODLAR.get(g.code ?? "");
      if (!k) return json(400, { error: "invalid_grant", detay: "kod taninmiyor" });
      if (k.kullanildi) { KODLAR.delete(g.code); return json(400, { error: "invalid_grant", detay: "kod ikinci kez sunuldu" }); }
      if (Date.now() > k.exp) return json(400, { error: "invalid_grant", detay: "kodun suresi gecti" });
      if (g.client_id !== k.client_id) return json(400, { error: "invalid_grant", detay: "istemci uyusmuyor" });
      if (g.redirect_uri !== k.redirect_uri) return json(400, { error: "invalid_grant", detay: "yonlendirme uyusmuyor" });
      if (!g.code_verifier || !esitMi(s256(g.code_verifier), k.meydan)) {
        return json(400, { error: "invalid_grant", detay: "dogrulayici uyusmuyor" });
      }
      k.kullanildi = true;
      return json(200, { access_token: erisimUret(k.sub, k.client_id, k.kapsam),
        token_type: "Bearer", expires_in: 600, scope: k.kapsam.join(" ") });
    }

    if (g.grant_type === "client_credentials") {
      const [ad, sir] = Buffer.from((istek.headers.authorization ?? "").replace("Basic ", ""), "base64")
        .toString("utf8").split(":");
      const istemci = ISTEMCILER.get(ad ?? "");
      if (!istemci || istemci.tur !== "gizli" || !esitMi(sir ?? "", istemci.sir)) return json(401, { error: "invalid_client" });
      const istenen = (g.scope ?? "").split(" ").filter(Boolean);
      if (!istenen.every((k) => istemci.kapsam.includes(k))) return json(400, { error: "invalid_scope" });
      return json(200, { access_token: erisimUret(ad, ad, istenen), token_type: "Bearer",
        expires_in: 600, scope: istenen.join(" ") });
    }

    if (g.grant_type === "urn:ietf:params:oauth:grant-type:device_code") {
      const k = AYGITLAR.get(g.device_code ?? "");
      if (!k) return json(400, { error: "invalid_grant" });
      if (!k.onay) return json(400, { error: "authorization_pending" });
      AYGITLAR.delete(g.device_code);
      return json(200, { access_token: erisimUret("U-1001", k.client_id, k.kapsam),
        token_type: "Bearer", expires_in: 600, scope: k.kapsam.join(" ") });
    }
    return json(400, { error: "unsupported_grant_type" });
  }
  json(404, { error: "not_found" });
}).listen(PORT, "127.0.0.1", () => console.log(`yetkilendirme sunucusu ${YAYINCI}`));
```

Yetkilendirme ucundaki denetimlerin sırası rastgele değildir. İstemci ya da yönlendirme adresi
tanınmıyorsa **yönlendirme yapılmaz**: hata, kaynak sahibinin tarayıcısında durur. Yönlendirme
adresinin doğrulanmadığı bir kurulumda hata yanıtı — ve daha kötüsü, yetki kodu — kayıtlı
olmayan bir adrese gönderilebilirdi. Yönlendirme adresi tam eşleşmeyle denetlenir; önek
eşleşmesi ya da joker kabul edilmez.

## Kaynak Sunucusu

Kaynak sunucusu belirteci doğrular ve uca göre gereken **kapsamı** (scope) arar.

```js
// kaynak-sunucusu.mjs — odunc servisi: belirteci dogrular ve kapsam denetler
// Kullanim: node kaynak-sunucusu.mjs [port]     (ontanimli 8552)
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";

const PORT = Number(process.argv[2] ?? 8552);
const ANAHTAR = Buffer.from("kimlik-saglayici-imza-anahtari-32bayt!!!", "utf8");
const GEREKEN = new Map([["GET", "odunc:oku"], ["POST", "odunc:yaz"]]);

const dogrula = (belirtec) => {
  const [govde, imza] = (belirtec ?? "").split(".");
  if (!govde || !imza) return null;
  const beklenen = createHmac("sha256", ANAHTAR).update(govde).digest("base64url");
  const a = Buffer.from(beklenen, "utf8"), b = Buffer.from(imza, "utf8");
  if (a.length !== b.length || !timingSafeEqual(a, b)) return null;
  const i = JSON.parse(Buffer.from(govde, "base64url").toString("utf8"));
  if (i.iss !== "http://127.0.0.1:8551" || i.aud !== "odunc-servisi") return null;
  return Math.floor(Date.now() / 1000) < i.exp ? i : null;
};

createServer((istek, yanit) => {
  yanit.sendDate = false;
  const json = (kod, govde) => {
    yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" });
    yanit.end(JSON.stringify(govde));
  };
  const b = istek.headers.authorization ?? "";
  const iddialar = b.startsWith("Bearer ") ? dogrula(b.slice(7)) : null;
  if (!iddialar) return json(401, { error: "invalid_token" });

  const gereken = istek.url.split("?")[0] === "/odunc" ? GEREKEN.get(istek.method) : undefined;
  if (!gereken) return json(404, { error: "not_found" });
  if (!iddialar.scope.split(" ").includes(gereken)) {
    return json(403, { error: "insufficient_scope", gereken, verilen: iddialar.scope });
  }
  json(istek.method === "GET" ? 200 : 201, { sub: iddialar.sub, istemci: iddialar.client_id, kapsam: iddialar.scope });
}).listen(PORT, "127.0.0.1", () => console.log(`kaynak sunucusu http://127.0.0.1:${PORT}`));
```

## İstemci

İstemci iki değer üretir ve ikisini de kendinde saklar: `state` ve PKCE **doğrulayıcısı**
(code verifier). Yetkilendirme sunucusuna doğrulayıcının kendisi değil, özeti — **meydan
okuma** (code challenge) — gönderilir.

```js
// istemci.mjs — okuma uygulamasi: akisi baslatir, geri donusu karsilar, belirteci alir
// Kullanim: node istemci.mjs [port]     (ontanimli 8553)
import { createServer } from "node:http";
import { createHash, randomBytes } from "node:crypto";

const PORT = Number(process.argv[2] ?? 8553);
const KENDI = `http://127.0.0.1:${PORT}`, AS = "http://127.0.0.1:8551", KAYNAK = "http://127.0.0.1:8552";
const BEKLEYEN = new Map();       // state -> PKCE dogrulayicisi; kayit istemcide durur
const s256 = (v) => createHash("sha256").update(v).digest("base64url");

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const adres = new URL(istek.url, KENDI);
  const json = (kod, govde) => {
    yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" });
    yanit.end(JSON.stringify(govde));
  };

  if (adres.pathname === "/basla") {
    const state = randomBytes(12).toString("base64url"), dogrulayici = randomBytes(32).toString("base64url");
    BEKLEYEN.set(state, dogrulayici);
    const u = new URL(`${AS}/yetkilendir`);
    for (const [ad, deger] of Object.entries({ response_type: "code", client_id: "okuma-uygulamasi",
      redirect_uri: `${KENDI}/geri`, scope: "odunc:oku", state,
      code_challenge: s256(dogrulayici), code_challenge_method: "S256" })) u.searchParams.set(ad, deger);
    return json(200, { yetkilendirmeAdresi: u.toString() });
  }

  if (adres.pathname === "/geri") {
    const state = adres.searchParams.get("state");
    // 1. state istemcinin kendi kaydiyla eslesmeli; eslesmiyorsa akis buraya ait degildir.
    if (!state || !BEKLEYEN.has(state)) return json(400, { hata: "state eslesmedi" });
    const dogrulayici = BEKLEYEN.get(state);
    BEKLEYEN.delete(state);                                  // state tek kullanimlik
    if (adres.searchParams.get("error")) return json(400, { hata: adres.searchParams.get("error") });

    // 2. Kod, dogrulayiciyla birlikte belirtece cevrilir.
    const cevap = await fetch(`${AS}/belirtec`, {
      method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({ grant_type: "authorization_code", code: adres.searchParams.get("code"),
        redirect_uri: `${KENDI}/geri`, client_id: "okuma-uygulamasi", code_verifier: dogrulayici }),
    });
    const belirtec = await cevap.json();
    if (!cevap.ok) return json(400, { hata: belirtec });

    // 3. Belirtecle kaynak sunucusu cagrilir.
    const kaynak = await fetch(`${KAYNAK}/odunc`, { headers: { authorization: `Bearer ${belirtec.access_token}` } });
    return json(200, { kapsam: belirtec.scope, kaynakDurumu: kaynak.status, kaynakGovdesi: await kaynak.json() });
  }
  json(404, { hata: "kaynak yok" });
}).listen(PORT, "127.0.0.1", () => console.log(`istemci ${KENDI}`));
```

İki değerin işlevi ayrıdır ve ikisi de zorunludur.

`state`, geri dönen isteğin istemcinin **kendi başlattığı** akışa ait olduğunu doğrular.
İstemci `state` değerini üretir, kendinde saklar ve geri dönüşte kayıtlı olup olmadığına
bakar; eşleşmeyen bir geri dönüş, istemcinin başlatmadığı bir akışın sonucudur ve reddedilir.
`state` denetlemeyen bir istemci, dışarıdan başlatılmış bir akışın kodunu kendi kullanıcısının
oturumuna bağlayabilir. Bu yüzden `state` üretilmemişse ya da denetlenmiyorsa kurulum
reddedilmelidir.

PKCE doğrulayıcısı, yetki kodunu **kodu isteyen istemciye** bağlar. Yetkilendirme sunucusu
kodu meydan okumayla birlikte saklar; belirteç isteğinde doğrulayıcının özeti bu meydan
okumayla karşılaştırılır. Doğrulayıcı istemcinin belleğinden hiç çıkmadığı için, kodu başka
bir yerden ele geçiren bir tarafın elinde onu belirteçle değiştirecek parça bulunmaz. Sır
saklayamayan açık istemciler için kodun tek koruması budur; bu yüzden `code_challenge`
gelmiyorsa ya da yöntem `S256` değilse istek reddedilmelidir.

## Akışın Uçtan Uca Çalıştırılması

Üç sunucu ayağa kaldırılır ve tarayıcının rolünü `curl` oynar: istemciden yetkilendirme
adresini alır, o adrese gider, dönen `Location` başlığını izler.

```bash
node yetkilendirme-sunucusu.mjs > /dev/null 2>&1 & Y=$!
node kaynak-sunucusu.mjs > /dev/null 2>&1 & K=$!
node istemci.mjs > /dev/null 2>&1 & I=$!
sleep 1.2
ADRES=$(curl -s http://127.0.0.1:8553/basla | sed -n 's/.*"yetkilendirmeAdresi":"\([^"]*\)".*/\1/p')
echo "1) yetkilendirme adresi:"; echo "   $ADRES"
GERI=$(curl -s -o /dev/null -w '%{redirect_url}' "$ADRES")
echo "2) geri dönüş adresi:"; echo "   $GERI"
echo "3) istemcinin sonucu:"; curl -s "$GERI"; echo
kill $Y $K $I
```

```
1) yetkilendirme adresi:
   http://127.0.0.1:8551/yetkilendir?response_type=code&client_id=okuma-uygulamasi&redirect_uri=http%3A%2F%2F127.0.0.1%3A8553%2Fgeri&scope=odunc%3Aoku&state=60fJ6tHMZO8KtTWD&code_challenge=upHf2ThVHNISKmkd0VCRtiA0Plr3VPQ0WFAzk1zrG4o&code_challenge_method=S256
2) geri dönüş adresi:
   http://127.0.0.1:8553/geri?code=-GL-lEji0qFimP42TeSl7aHGnEm_jTOh&state=60fJ6tHMZO8KtTWD
3) istemcinin sonucu:
{"kapsam":"odunc:oku","kaynakDurumu":200,"kaynakGovdesi":{"sub":"U-1001","istemci":"okuma-uygulamasi","kapsam":"odunc:oku"}}
```

`state`, meydan okuma ve kod her çalıştırmada değişir. İkinci satırda tarayıcıdan geçen tek
şeyin kod olduğu görülüyor; belirteç hiçbir adreste görünmüyor. Kaynak sunucusunun yanıtında
ise `sub` üyeyi, `istemci` uygulamayı ayrı ayrı bildiriyor: kaynak sunucusu isteğin kimin
adına ve hangi uygulama eliyle geldiğini birbirinden ayırabiliyor.

## Belirteç Ucundaki Denetimler

Aşağıdaki blok kodu doğrudan belirteç ucunda dener; doğrulayıcı ve meydan okuma kabukta
üretilir.

```bash
node yetkilendirme-sunucusu.mjs > /dev/null 2>&1 & Y=$!
sleep 1
AS=http://127.0.0.1:8551
GERI=http://127.0.0.1:8553/geri
DOGRULAYICI=$(head -c 32 /dev/urandom | base64 | tr '+/' '-_' | tr -d '=')
MEYDAN=$(printf '%s' "$DOGRULAYICI" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=')
kodAl() {
  curl -s -o /dev/null -w '%{redirect_url}' \
    "$AS/yetkilendir?response_type=code&client_id=okuma-uygulamasi&redirect_uri=$GERI&scope=odunc:oku&state=abc&code_challenge=$MEYDAN&code_challenge_method=S256" \
    | sed -n 's/.*[?&]code=\([^&]*\).*/\1/p'
}
degis() { curl -s -d "$1" "$AS/belirtec" | sed 's/"access_token":"[^"]*"/"access_token":"…"/'; }
KOD=$(kodAl)
echo "gecerli degisim     : $(degis "grant_type=authorization_code&code=$KOD&redirect_uri=$GERI&client_id=okuma-uygulamasi&code_verifier=$DOGRULAYICI")"
echo "ayni kod ikinci kez : $(degis "grant_type=authorization_code&code=$KOD&redirect_uri=$GERI&client_id=okuma-uygulamasi&code_verifier=$DOGRULAYICI")"
KOD=$(kodAl)
echo "dogrulayici yok     : $(degis "grant_type=authorization_code&code=$KOD&redirect_uri=$GERI&client_id=okuma-uygulamasi")"
KOD=$(kodAl)
echo "baska yonlendirme   : $(degis "grant_type=authorization_code&code=$KOD&redirect_uri=http://127.0.0.1:8553/baska&client_id=okuma-uygulamasi&code_verifier=$DOGRULAYICI")"
kill $Y
```

```
gecerli degisim     : {"access_token":"…","token_type":"Bearer","expires_in":600,"scope":"odunc:oku"}
ayni kod ikinci kez : {"error":"invalid_grant","detay":"kod ikinci kez sunuldu"}
dogrulayici yok     : {"error":"invalid_grant","detay":"dogrulayici uyusmuyor"}
baska yonlendirme   : {"error":"invalid_grant","detay":"yonlendirme uyusmuyor"}
```

Kod tek kullanımlıktır ve ikinci sunuluşunda kayıt tümüyle düşürülür — yenileme belirteçlerinde
kurulan yeniden kullanım saptamasının aynısıdır. Doğrulayıcının bulunmaması ile yanlış bir
doğrulayıcı sunulması aynı yanıtı alır; ayrım yapmak, dışarıya bir bilgi sızdırmaktan başka
bir işe yaramaz.

## İki Akış Daha

**İstemci kimlik bilgisi akışı** (client credentials), kaynak sahibinin bulunmadığı durum
içindir: raf terminali kendi adına çalışır, kimliğini kendi sırrıyla kanıtlar ve kapsamı kendi
kaydından gelir. **Aygıt akışı** (device flow), tarayıcı açamayan aygıtlar içindir: aygıt bir
kullanıcı kodu alır, kullanıcı bu kodu başka bir aygıtta onaylar, aygıt bu sırada yoklama
yapar.

```bash
node yetkilendirme-sunucusu.mjs > /dev/null 2>&1 & Y=$!
node kaynak-sunucusu.mjs > /dev/null 2>&1 & K=$!
sleep 1
AS=http://127.0.0.1:8551
kisalt() { sed 's/"access_token":"[^"]*"/"access_token":"…"/'; }
YANIT=$(curl -s -u raf-terminali:raf-terminali-sirri-1 -d 'grant_type=client_credentials&scope=odunc:oku odunc:yaz' $AS/belirtec)
echo "$YANIT" | kisalt
BELIRTEC=$(echo "$YANIT" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
echo "POST /odunc: $(curl -s -X POST -H "Authorization: Bearer $BELIRTEC" http://127.0.0.1:8552/odunc)"
YALNIZ=$(curl -s -u raf-terminali:raf-terminali-sirri-1 -d 'grant_type=client_credentials&scope=odunc:oku' $AS/belirtec | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
echo "yalniz okuma kapsamiyla POST /odunc: $(curl -s -X POST -H "Authorization: Bearer $YALNIZ" http://127.0.0.1:8552/odunc)"
AYGIT=$(curl -s -d 'client_id=raf-terminali&scope=odunc:oku' $AS/aygit/kod)
echo "$AYGIT"
AYGIT_KODU=$(echo "$AYGIT" | sed -n 's/.*"device_code":"\([^"]*\)".*/\1/p')
KULLANICI_KODU=$(echo "$AYGIT" | sed -n 's/.*"user_code":"\([^"]*\)".*/\1/p')
echo "onay oncesi yoklama : $(curl -s -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=$AYGIT_KODU" $AS/belirtec)"
curl -s -o /dev/null -d "user_code=$KULLANICI_KODU" $AS/aygit/onay
echo "onay sonrasi yoklama: $(curl -s -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=$AYGIT_KODU" $AS/belirtec | kisalt)"
kill $Y $K
```

```
{"access_token":"…","token_type":"Bearer","expires_in":600,"scope":"odunc:oku odunc:yaz"}
POST /odunc: {"sub":"raf-terminali","istemci":"raf-terminali","kapsam":"odunc:oku odunc:yaz"}
yalniz okuma kapsamiyla POST /odunc: {"error":"insufficient_scope","gereken":"odunc:yaz","verilen":"odunc:oku"}
{"device_code":"EafPu8Lm73tmn2_R8jKGtWd3","user_code":"613D0C","verification_uri":"http://127.0.0.1:8551/aygit","expires_in":600,"interval":1}
onay oncesi yoklama : {"error":"authorization_pending"}
onay sonrasi yoklama: {"access_token":"…","token_type":"Bearer","expires_in":600,"scope":"odunc:oku"}
```

Aygıt ve kullanıcı kodları her çalıştırmada değişir. Üçüncü satır kapsamın işini gösteriyor:
aynı istemci, yalnız okuma kapsamıyla aldığı belirteçle yazma ucunda 403 alıyor. Kapsam,
belirtecin kime ait olduğunu değil, **neye izinli** olduğunu daraltır. Aygıt akışında ise
yoklama, onay gelene kadar `authorization_pending` alır; aygıtın yanıtta bildirilen `interval`
değerinden sık yoklamaması gerekir.

## Özet

- Yetki devri dört rol tanımlar: kaynak sahibi, istemci, yetkilendirme sunucusu ve kaynak
  sunucusu. Kaynak sahibi kimlik bilgisini yalnız yetkilendirme sunucusuna verir.
- Yetki kodu akışında tarayıcıdan yalnız kısa ömürlü kod geçer; belirteç, tarayıcıdan
  geçmeyen ayrı bir istekle alınır.
- Yönlendirme adresi tam eşleşmeyle denetlenir ve tanınmayan adrese hiçbir şey gönderilmez;
  `state` üretilmemiş ya da denetlenmiyorsa, `code_challenge` yoksa ya da yöntem `S256`
  değilse kurulum reddedilmelidir.
- Yetki kodu tek kullanımlıktır; ikinci sunuluşunda kayıt düşürülür ve doğrulayıcı
  uyuşmazlığı ile eksikliği aynı yanıtı alır.
- İstemci kimlik bilgisi akışı kaynak sahibinin bulunmadığı makine çağrıları içindir; aygıt
  akışı tarayıcı açamayan aygıtlar için onayı başka bir aygıta taşır.
- Kapsam, belirtecin neye izinli olduğunu daraltır; kaynak sunucusu uca göre gereken kapsamı
  arar ve eksikse 403 döner.

## Sonraki Adım

Bu akışların hiçbiri "bu kullanıcı kim?" sorusunu yanıtlamadı. Kaynak sunucusu belirteçteki
`sub` alanını okudu ama o alanın ne anlama geldiği, hangi kullanıcıya karşılık geldiği ve
belirtecin gerçekten bir kimlik doğrulama sonucunda üretilip üretilmediği yetki devri
protokolünün kapsamı dışındadır. İstemcinin kullanıcıyı tanıması gerekiyorsa — adını
göstermek, hesabını eşleştirmek, oturumunu açmak için — ayrı bir katman gerekir. Sonraki ders
bu katmanı kurar: yetkilendirme sunucusu istemciye erişim belirtecinin yanında bir **kimlik
belirteci** verir ve istemci onu yayıncı, hedef kitle, süre ve imza adımlarıyla doğrular.
