Ders 07 / 23
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ı.
İçindekiler
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
// 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.
// 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.
// 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.
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.
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.
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_challengeyoksa ya da yöntemS256değ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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.