Kısa cevap: API anahtarı (API key), bir uygulamaya ya da servise verilen uzun ve tahmin edilemez bir gizli değerdir. İstemci bu değeri her istekte Authorization: Bearer sk_live_... başlığıyla gönderir; sunucu anahtarı kayıtlarında arar, bulursa isteği o istemci adına işler, bulamazsa ya da anahtar iptal edilmişse 401 döner. Anahtar geçerli ama istenen işlem için izni yoksa yanıt 403 olur. Bu yöntem basittir ve özellikle bir sunucunun başka bir sunucuyu çağırdığı servisler arası (service-to-service) kullanım için uygundur.

Bu yazıda anahtarın nasıl gönderileceğini, nasıl üretilip saklanacağını, FastAPI ile nasıl doğrulanacağını ve yaşam döngüsünü anlatıyoruz. Kullanıcı oturumları için kullanılan JWT akışı JWT Bearer token yazısının, 401 ile 403 arasındaki sınır ise 401 ve 403 farkı yazısının konusu. Serinin tamamı REST API tasarımı öğrenme rehberi sayfasında.

API anahtarı neyi kanıtlar?

API anahtarı neyi kanıtlar? Kimi tanımlar, Sunucu doğrularken, Ömür, İptal

API anahtarı bir kişiyi değil, bir istemciyi tanımlar. Fatura servisiniz sipariş API'nizi çağırıyorsa, anahtar "bu istek fatura servisinden geliyor" bilgisini taşır. Anahtarın kendisinde bir anlam yoktur; anlam, sunucunun o anahtarla eşleştirdiği kayıtta durur: hangi müşteriye ait olduğu, hangi izinleri taşıdığı, aktif olup olmadığı.

Skill bu yöntemi "basit, servisler arası kullanım için iyi" olarak tarif eder, JWT'yi ise "durumsuz, kullanıcı kimlik doğrulaması için iyi" diye ayırır. Bu ayrımın sebebi tasarımın kendisindedir. Anahtar genellikle uzun ömürlüdür ve sunucu her istekte onu kayıtlarında arar. Bu sayede bir anahtarı iptal ettiğiniz anda etkisi biter. Buna karşılık anahtar, onu taşıyan herkesin kullanabileceği bir değerdir: Bir tarayıcı uygulamasının JavaScript kodunun ya da dağıtılan bir mobil uygulamanın içine gömülen anahtar, kodu inceleyen herkes tarafından okunabilir. Bu yüzden API anahtarları en iyi, gizli değeri koruyabilen sunucu tarafı istemcilerde çalışır.

ÖzellikAPI anahtarıJWT
Kimi tanımlarUygulama veya servisGenellikle oturum açmış kullanıcı
Sunucu doğrularkenKayıtlarda ararİmzayı ve süreyi kontrol eder
ÖmürUzun, elle iptal edilene kadarKısa, exp alanıyla sınırlı
İptalKayıt pasifleştirildiğinde hemenSüre dolana kadar zor
Uygun istemciSunucu tarafı servislerKullanıcı oturumu olan uygulamalar

Anahtar nasıl gönderilir?

Skill, anahtarı JWT ile aynı biçimde, Authorization başlığında Bearer şemasıyla göndermeyi gösterir:

http
GET /v1/siparisler HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_deneme123

Burada küçük ama dürüst bir ayrım yapalım: Bearer şemasını tanımlayan RFC 6750, OAuth 2.0 erişim token'ları için yazılmıştır. Bir API anahtarını bu şemayla göndermek skill'in önerdiği bir tasarım tercihidir, standardın kendisi değildir. Yine de RFC 6750'nin "bearer token" tanımı API anahtarına birebir uyar: Token'ı elinde bulunduran her taraf, onu diğer sahipleriyle aynı şekilde kullanabilir; kullanmak için bir kriptografik anahtara sahip olduğunu kanıtlaması gerekmez. Bu tanım, anahtarın neden bir parola kadar dikkatle korunması gerektiğini de özetler.

Standart Authorization başlığını kullanmanın teknik bir faydası da vardır. RFC 9110, bir proxy'nin ilettiği isteklerdeki Authorization alanını değiştirmemesi gerektiğini yazar ve önbelleklerin bu başlığı taşıyan istekleri özel kurallarla ele almasını HTTP önbellek standardına bağlar. Kendi uydurduğunuz bir başlık bu korumalardan otomatik olarak yararlanmaz.

Anahtarı URL'ye koymaktan kaçının. RFC 6750, token'ı sorgu parametresiyle göndermeyi belgeler ama önermez: URL'nin günlüklere yazılma ihtimali yüksektir, tarayıcı geçmişinde ve sunucu kayıtlarında kalabilir. Standart bu yöntemin yalnızca başlıkla ya da gövdeyle gönderim mümkün değilse kullanılmasını söyler. Aşağıdaki istek bu yüzden kötü bir örnektir:

http
GET /v1/siparisler?api_key=sk_live_deneme123 HTTP/1.1
Host: api.example.com

Son olarak, anahtarlar her zaman TLS (https) üzerinden gönderilmelidir. RFC 6750 bearer token'lar için istemcinin her zaman TLS kullanmasını ve sertifika zincirini doğrulamasını zorunlu tutar; aynı mantık API anahtarı için de geçerlidir.

Anahtar biçimi: önek ve rastgelelik

Anahtar biçimi: önek ve rastgelelik

Skill'in örneklerinde anahtarlar sk_live_ veya sk- gibi bir önekle başlar. Bu bir standart değil, işe yarayan bir fikirdir. Önek, bir anahtarın ne olduğunu bakar bakmaz anlatır: sk_live_ canlı ortamın gizli anahtarı, sk_test_ deneme ortamınınki olabilir. Böylece test anahtarıyla canlı ortama istek atma gibi hatalar sunucu tarafında öneke bakılarak erkenden reddedilebilir; bir günlük dosyasında ya da kod deposunda unutulmuş bir anahtarı aramak da kolaylaşır.

Önekten sonraki kısım tamamen rastgele olmalıdır. RFC 6750, token'ın yetki bilgisine bir referans olduğu durumda bu referansın saldırgan tarafından tahmin edilmesinin pratikte imkânsız olması gerektiğini söyler. Aşağıdaki örnekte bunun için Python standart kütüphanesindeki secrets modülünü kullanıyoruz.

python
import hashlib
import secrets

def anahtar_uret(ortam: str) -> tuple[str, str]:
    """Yeni anahtarı ve veritabanına yazılacak özetini döndürür."""
    anahtar = f"sk_{ortam}_" + secrets.token_urlsafe(32)
    ozet = hashlib.sha256(anahtar.encode()).hexdigest()
    return anahtar, ozet

anahtar, ozet = anahtar_uret("live")
print(anahtar.startswith("sk_live_"), len(anahtar), len(ozet))
# çıktı: True 51 64
print(anahtar[:8])
# çıktı: sk_live_

token_urlsafe(32) 32 rastgele bayttan URL'de güvenle kullanılabilen 43 karakterlik bir metin üretir; önekle birlikte anahtar 51 karakter olur. Üretilen karakterler (harfler, rakamlar, - ve _) RFC 6750'nin Bearer kimlik bilgisi için tanımladığı b64token söz dizimine uyar, yani başlıkta özel bir kodlamaya gerek kalmaz. Fonksiyon anahtarın yanında SHA-256 özetini de döndürüyor; nedenini bir sonraki bölümde anlatıyoruz.

Sunucuda saklama: Anahtarı değil özetini tutun

Veritabanınız bir gün sızarsa, içinde düz metin API anahtarları bulunması bütün müşterilerinizin hesabının ele geçmesi demektir. Bu riski azaltmanın bir yolu, anahtarı yalnızca oluşturulduğu anda kullanıcıya bir kez göstermek ve veritabanına anahtarın kendisi yerine tek yönlü özetini (hash) yazmaktır. Gelen istekte sunucu, başlıktaki değerin özetini hesaplar ve kayıtlarda bu özeti arar.

Burada SHA-256 gibi hızlı bir özetin yeterli görülmesinin gerekçesi, bir parolanın aksine anahtarın insan tarafından seçilmemiş olmasıdır: 32 baytlık rastgele bir değeri özetinden geriye doğru tahmin etmek, parolalarda işe yarayan sözlük saldırılarıyla mümkün değildir. Bu bir tasarım değerlendirmesidir; kendi güvenlik gereksinimlerinize göre karar verin.

Özet saklamanın kullanıcı deneyimine bir etkisi vardır: Anahtar kaybolursa size geri gösterilemez, yalnızca yenisi üretilebilir. Kullanıcıların anahtarlarını panelde ayırt edebilmesi için önekle birlikte ilk birkaç karakteri ayrı bir alanda saklamak işe yarar. Skill'in kontrol listesindeki "yanıtlarda sır yok" maddesi de buraya bağlanır: Anahtar listeleyen bir uç nokta tam anahtarı asla döndürmemelidir. Diğer sık hatalar için REST API tasarım hataları yazısına bakabilirsiniz.

FastAPI ile anahtar doğrulama

Aşağıdaki uygulama iki katmanlı bir kontrol yapıyor. api_anahtari bağımlılığı "bu anahtar geçerli mi" sorusunu cevaplıyor ve geçersizse 401 dönüyor. izin() ise bir bağımlılık üreten küçük bir fonksiyon; her uç nokta hangi izni istediğini söylüyor ve anahtarda o izin yoksa 403 dönülüyor. Anahtar tablosu bellekte bir sözlük; gerçek uygulamada veritabanında durur.

python
import hashlib

from fastapi import Depends, FastAPI, HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi.testclient import TestClient

app = FastAPI()
bearer = HTTPBearer(auto_error=False)

def ozet(anahtar: str) -> str:
    return hashlib.sha256(anahtar.encode()).hexdigest()

# Gerçek uygulamada bu tablo veritabanında durur; anahtarın kendisi değil özeti saklanır.
ANAHTARLAR = {
    ozet("sk_live_deneme123"): {"musteri": "fatura-servisi", "izinler": {"siparis:oku"}, "aktif": True},
    ozet("sk_live_eski456"): {"musteri": "eski-entegrasyon", "izinler": {"siparis:oku"}, "aktif": False},
}
YETKISIZ = {"WWW-Authenticate": 'Bearer realm="siparis-api", error="invalid_token"'}

def api_anahtari(cred: HTTPAuthorizationCredentials | None = Depends(bearer)) -> dict:
    if cred is None:
        raise HTTPException(401, "API anahtarı gerekli", headers={"WWW-Authenticate": 'Bearer realm="siparis-api"'})
    bilgi = ANAHTARLAR.get(ozet(cred.credentials))
    if bilgi is None or not bilgi["aktif"]:
        raise HTTPException(401, "API anahtarı geçersiz veya iptal edilmiş", headers=YETKISIZ)
    return bilgi

def izin(gerekli: str):
    def kontrol(istemci: dict = Depends(api_anahtari)) -> dict:
        if gerekli not in istemci["izinler"]:
            raise HTTPException(403, f"Bu anahtarın '{gerekli}' izni yok")
        return istemci
    return kontrol

@app.get("/v1/siparisler")
def listele(istemci: dict = Depends(izin("siparis:oku"))):
    return {"musteri": istemci["musteri"], "data": []}

@app.post("/v1/siparisler", status_code=201)
def olustur(istemci: dict = Depends(izin("siparis:yaz"))):
    return {"id": 1}

c = TestClient(app)
print(c.get("/v1/siparisler").status_code)
# çıktı: 401
r = c.get("/v1/siparisler", headers={"Authorization": "Bearer sk_live_eski456"})
print(r.status_code, r.json())
# çıktı: 401 {'detail': 'API anahtarı geçersiz veya iptal edilmiş'}
r = c.get("/v1/siparisler", headers={"Authorization": "Bearer sk_live_deneme123"})
print(r.status_code, r.json())
# çıktı: 200 {'musteri': 'fatura-servisi', 'data': []}
r = c.post("/v1/siparisler", headers={"Authorization": "Bearer sk_live_deneme123"})
print(r.status_code, r.json())
# çıktı: 403 {'detail': "Bu anahtarın 'siparis:yaz' izni yok"}

Çıktıları sırayla değerlendirelim. Başlıksız istek 401 alıyor ve WWW-Authenticate başlığı gönderiliyor. Pasif hâle getirilmiş eski anahtar, kayıtta bulunduğu hâlde 401 alıyor; istemci açısından iptal edilmiş bir anahtar ile hiç var olmamış bir anahtar arasında fark yoktur ve RFC 6750'nin invalid_token tanımı iptal edilmiş token'ı da kapsar. Geçerli anahtar listeleme için 200 alıyor, ama aynı anahtarla sipariş oluşturma 403 ile reddediliyor, çünkü bu anahtara yalnızca okuma izni verilmiş.

Skill'deki örnek tek bir validate_api_key() çağrısıyla yetinir. İzinleri anahtar başına tutmak, bir entegrasyonun ihtiyaç duyduğundan fazla yetkiyle çalışmasını engeller: Raporlama servisinin anahtarı sızsa bile sipariş silemez. RFC 6750 de token'ların kullanımını belirli bir kapsamla sınırlamayı önerir.

Hata yanıtında ne kadar bilgi verilmeli?

Anahtar doğrulaması başarısız olduğunda iki ayrı durumu ayırmak işe yarar. Başlık hiç yoksa istemci büyük ihtimalle belgeleri okumamıştır; RFC 6750 bu durumda hata kodu eklemeden yalnızca şemayı ve realm değerini göndermeyi önerir. Başlık var ama anahtar tanınmıyorsa error="invalid_token" eklenir ve istemci yeni bir anahtar edinmesi gerektiğini anlar.

http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="siparis-api", error="invalid_token"
Content-Type: application/json

{"error": "API anahtarı geçersiz veya iptal edilmiş", "code": "INVALID_API_KEY"}

Bunun ötesine geçmeyin. "Bu anahtar vardı ama dün iptal edildi" ya da "bu anahtar başka bir müşteriye ait" gibi mesajlar, anahtarları deneme yanılmayla tarayan birine fazladan bilgi verir. Örnekteki uygulama iptal edilmiş ve hiç var olmamış anahtarlara bu yüzden aynı mesajı döndürüyor. Ayrıntılı neden, destek ekibinin bakabileceği sunucu günlüklerinde durmalıdır. Günlüğe anahtarın kendisini değil, eşleşen kaydın kimliğini ya da müşteri adını yazmak, hem denetim izi bırakır hem de günlük dosyasını bir sır deposuna dönüştürmez.

Bir ayrıntı daha: İstek ortamıyla uyuşmayan bir anahtar, örneğin canlı API'ye gönderilen bir sk_test_ anahtarı, yine 401 almalıdır. Burada daha açıklayıcı bir mesaj vermek zarar vermez, çünkü önek zaten anahtarın türünü herkese gösterir ve geliştiricinin hatasını hızla bulmasını sağlar.

Anahtarın yaşam döngüsü

Anahtarın yaşam döngüsü: Oluşturma, Kullanım, Sınırlama, Döndürme (rotation)
  • Oluşturma: Anahtarı rastgele üretin, kullanıcıya bir kez gösterin, veritabanına özetini, önekini, sahibini ve izinlerini yazın.
  • Kullanım: Her istekte özeti arayın; isterseniz son kullanım zamanını kaydedin. Kullanılmayan anahtarları bulmak böylece kolaylaşır.
  • Sınırlama: İstek sınırını IP yerine anahtar başına uygulamak, aynı ağın arkasındaki farklı müşterileri birbirinden ayırır. Algoritmalar için API rate limiting stratejileri yazısına bakın.
  • Döndürme (rotation): Müşterinin aynı anda iki aktif anahtarı olabilmesine izin verin. Yeni anahtar dağıtılır, eskisi bir süre sonra iptal edilir; böylece kesinti yaşanmaz.
  • İptal: Sızdığından şüphelenilen anahtarı hemen pasif yapın. Sunucu her istekte kayda baktığı için etkisi anında başlar.

Sık yapılan hatalar

  • Anahtarı tarayıcı veya mobil uygulama koduna gömmek. İstemci kodu kullanıcının elindedir; içindeki anahtar gizli kalmaz.
  • Anahtarı sorgu parametresinde göndermek. URL'ler günlüklere ve geçmişe yazılır; RFC 6750 de bunu önermez.
  • Anahtarı kod deposuna yazmak. Anahtarlar ortam değişkenlerinden veya bir gizli bilgi yöneticisinden okunmalıdır.
  • Düz metin saklamak. Veritabanı sızıntısı bütün anahtarların sızması anlamına gelir.
  • Authorization başlığını günlüğe yazmak. İstek günlüklerinde bu başlığı maskeleyin; aksi hâlde günlük dosyası bir anahtar deposuna dönüşür.
  • Test ve canlı ortamda aynı anahtarı kullanmak. Önek ve ayrı anahtarlar, bir ortamdaki hatanın diğerine taşınmasını önler.
  • Tek anahtara her izni vermek. Sızıntının etkisini sınırlamak için izinleri entegrasyonun ihtiyacına göre daraltın. Genel güvenlik çerçevesi için REST API güvenlik rehberine bakın.

Sık Sorulan Sorular

API anahtarını hangi başlıkta göndermeliyim?

Skill'in önerisi Authorization: Bearer biçimidir. Bu, standart kimlik doğrulama alanını kullandığı için proxy ve önbellek davranışı açısından en öngörülebilir seçenektir. Hangi biçimi seçerseniz seçin, belgelerinizde açıkça yazın ve tüm uç noktalarda aynı yolu kullanın.

Anahtarın kendisini neden tekrar gösteremiyorum?

Sunucu yalnızca özeti sakladığı için anahtarın kendisi artık hiçbir yerde yoktur. Bu bilinçli bir tasarımdır: Veritabanı sızsa bile özetlerden çalışan anahtarlar elde edilemez. Kaybolan anahtarın yerine yenisini üretip eskisini iptal edersiniz.

Kullanıcı girişi için API anahtarı kullanabilir miyim?

Teknik olarak mümkündür ama uzun ömürlü bir gizli değerin kullanıcı cihazlarında dolaşması riski büyütür. Kullanıcı oturumları için kısa ömürlü token'lar daha uygundur; akış JWT Bearer token yazısında anlatılıyor.

İptal edilen anahtar için 401 mi 403 mü dönmeliyim?

401. İptal edilmiş bir anahtar artık geçerli bir kimlik bilgisi değildir; RFC 6750'nin invalid_token açıklaması da süresi dolmuş ve iptal edilmiş token'ları birlikte sayar. 403, geçerli bir anahtarın izni yetmediğinde kullanılır.

Bir müşteriye birden fazla anahtar vermeli miyim?

Evet, bunun iki faydası vardır. Birincisi döndürmedir: Yeni anahtar devreye alınırken eskisi bir süre daha çalışır ve geçiş kesintisiz olur. İkincisi ayrıştırmadır: Müşterinin raporlama ve ödeme entegrasyonları ayrı anahtarlar kullanırsa, birinin sızması diğerini etkilemez ve her anahtara yalnızca gereken izinler verilir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: REST API tasarımı öğrenme rehberi · 401 ve 403 farkı · JWT Bearer token kimlik doğrulama · API rate limiting · REST API güvenlik rehberi · REST API tasarım hataları · Durum kodu seçimi