API Kimlik Doğrulama Yöntemleri: Bearer Token Kullanımı
Modern uygulamalarda API’ler artık sadece bir özellik değil, sistemlerin omurgası haline geldi. Bir mikroservis mimarisi kuruyorsun, üçüncü parti bir servisle entegrasyon yapıyorsun ya da mobil uygulamana backend yazıyorsun; her durumda kimlik doğrulama meselesini doğru çözmek zorundasın. Yanlış yapılandırılmış bir auth mekanizması, en güzel yazılmış API’yi bile güvenlik açığına dönüştürür. Bu yazıda Bearer Token yöntemini, teorik kısmı bir kenara bırakarak gerçek dünya senaryoları üzerinden ele alacağız.
Bearer Token Nedir ve Neden Kullanırız
Bearer Token, HTTP Authorization header’ı üzerinden iletilen, “bu token’a sahip olan kişi erişim hakkına sahiptir” prensibine dayanan bir kimlik doğrulama yöntemidir. RFC 6750 standardıyla tanımlanmıştır ve OAuth 2.0 framework’ünün ayrılmaz bir parçasıdır.
İsim biraz yanıltıcı gelebilir. “Bearer” yani “taşıyıcı” demek; token’ı kim taşıyorsa o kişinin yetkili kabul edilmesi anlamına gelir. Bu yüzden token güvenliği kritik önem taşır. Token ele geçirilirse, saldırgan sisteme doğrudan erişebilir.
Neden Basic Auth değil de Bearer Token?
- Basic Auth’ta her istekte kullanıcı adı ve şifre gönderilir, bu ciddi bir risk
- Bearer Token’lar süre sınırlıdır, ele geçirilse bile belirli bir süre sonra geçersiz olur
- Granüler yetkilendirme (scope) desteği sunar
- Stateless yapısı sayesinde yatay ölçekleme kolaylaşır
- Kullanıcı kimlik bilgileri yerine token iptal etmek yeterlidir
Token Türleri: Opaque vs JWT
Pratikte iki tür Bearer Token ile karşılaşırsın:
Opaque Token: Rastgele üretilmiş, anlamlı bir içeriği olmayan string. Doğrulama için mutlaka token store’a (Redis, veritabanı) bakman gerekir.
JWT (JSON Web Token): Header, payload ve signature’dan oluşan, kendi içinde bilgi taşıyan token. Doğrulama için dışarıya bakman gerekmez, signature’ı verify edersin.
Çoğu modern sistemde JWT tercih edilir çünkü stateless doğrulama sağlar. Ancak token iptali konusunda opaque token’lar daha avantajlıdır.
Temel Bearer Token Kullanımı
En basit haliyle bir API’ye Bearer Token ile istek atmak şöyle görünür:
curl -X GET https://api.example.com/v1/users
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Header formatı standarttır: Authorization: Bearer . Aralarındaki boşluğa dikkat et, bazı implementasyonlar buna hassastır.
Token almak için genellikle önce bir authentication endpoint’ine istek atarsın:
# Token almak için kimlik bilgilerini gönder
curl -X POST https://api.example.com/auth/token
-H "Content-Type: application/json"
-d '{
"username": "admin",
"password": "supersecret",
"grant_type": "password"
}'
# Dönen yanıt örneği:
# {
# "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
# "token_type": "Bearer",
# "expires_in": 3600,
# "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."
# }
Gerçek Dünya Senaryo 1: Basit Token Yönetimi Script’i
Bir script yazıyorsun ve bu script’in her çalışmasında token alıp işlem yapması gerekiyor. Token’ı cache’lemek ve expire olmadan önce yenilemek istiyorsun:
#!/bin/bash
TOKEN_FILE="/tmp/.api_token"
TOKEN_EXPIRY_FILE="/tmp/.api_token_expiry"
API_BASE="https://api.example.com/v1"
AUTH_URL="https://api.example.com/auth/token"
get_token() {
local response
response=$(curl -s -X POST "$AUTH_URL"
-H "Content-Type: application/json"
-d "{"username": "${API_USER}", "password": "${API_PASS}"}")
echo "$response" | jq -r '.access_token' > "$TOKEN_FILE"
# Token'ın expire süresini hesapla (şu anki zaman + expires_in)
local expires_in
expires_in=$(echo "$response" | jq -r '.expires_in')
echo $(($(date +%s) + expires_in - 60)) > "$TOKEN_EXPIRY_FILE"
}
ensure_valid_token() {
local current_time
current_time=$(date +%s)
# Token dosyası yoksa veya expire olduysa yeni token al
if [[ ! -f "$TOKEN_FILE" ]] || [[ ! -f "$TOKEN_EXPIRY_FILE" ]]; then
echo "Token bulunamadi, yeni token aliniyor..."
get_token
return
fi
local expiry
expiry=$(cat "$TOKEN_EXPIRY_FILE")
if [[ "$current_time" -ge "$expiry" ]]; then
echo "Token suresi doldu, yenileniyor..."
get_token
fi
}
api_request() {
local method="$1"
local endpoint="$2"
local data="$3"
ensure_valid_token
local token
token=$(cat "$TOKEN_FILE")
curl -s -X "$method" "${API_BASE}${endpoint}"
-H "Authorization: Bearer $token"
-H "Content-Type: application/json"
${data:+-d "$data"}
}
# Kullanım
api_request "GET" "/users"
api_request "POST" "/users" '{"name": "Ahmet Yilmaz", "email": "[email protected]"}'
JWT’nin Anatomisi
JWT’yi anlamak token güvenliğini doğru uygulamak için şart. Bir JWT üç bölümden oluşur ve nokta ile ayrılır:
# JWT decode etmek (imzayı doğrulamadan sadece içeriği görmek)
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
# Header'ı decode et
echo "$TOKEN" | cut -d'.' -f1 | base64 -d 2>/dev/null | jq .
# Çıktı: {"alg": "HS256", "typ": "JWT"}
# Payload'ı decode et
echo "$TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .
# Çıktı: {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
# Token'ın expire olup olmadığını kontrol et
check_jwt_expiry() {
local token="$1"
local payload
payload=$(echo "$token" | cut -d'.' -f2 | base64 -d 2>/dev/null)
local exp
exp=$(echo "$payload" | jq -r '.exp // empty')
if [[ -z "$exp" ]]; then
echo "Token'da exp claim'i yok"
return 1
fi
local current_time
current_time=$(date +%s)
if [[ "$current_time" -gt "$exp" ]]; then
echo "Token expire olmus: $(date -d @$exp)"
return 1
else
local remaining=$(( exp - current_time ))
echo "Token gecerli. Kalan sure: ${remaining} saniye"
return 0
fi
}
Gerçek Dünya Senaryo 2: Refresh Token Akışı
Access token’lar kasıtlı olarak kısa ömürlüdür (genellikle 15 dakika ile 1 saat arası). Refresh token’lar ise kullanıcıyı tekrar giriş yapmaya zorlamadan yeni access token almayı sağlar:
#!/bin/bash
ACCESS_TOKEN_FILE="/var/cache/myapp/access_token"
REFRESH_TOKEN_FILE="/var/cache/myapp/refresh_token"
AUTH_URL="https://api.example.com/oauth/token"
refresh_access_token() {
local refresh_token
refresh_token=$(cat "$REFRESH_TOKEN_FILE" 2>/dev/null)
if [[ -z "$refresh_token" ]]; then
echo "HATA: Refresh token bulunamadi, yeniden giris gerekiyor" >&2
return 1
fi
local response
response=$(curl -s -w "n%{http_code}" -X POST "$AUTH_URL"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=refresh_token"
-d "refresh_token=${refresh_token}"
-d "client_id=${CLIENT_ID}"
-d "client_secret=${CLIENT_SECRET}")
local http_code
http_code=$(echo "$response" | tail -n1)
local body
body=$(echo "$response" | head -n-1)
if [[ "$http_code" == "200" ]]; then
echo "$body" | jq -r '.access_token' > "$ACCESS_TOKEN_FILE"
# Bazı sunucular yeni refresh token da döner
local new_refresh
new_refresh=$(echo "$body" | jq -r '.refresh_token // empty')
if [[ -n "$new_refresh" ]]; then
echo "$new_refresh" > "$REFRESH_TOKEN_FILE"
fi
echo "Token basariyla yenilendi"
return 0
elif [[ "$http_code" == "401" ]]; then
echo "HATA: Refresh token gecersiz, yeniden kimlik dogrulama gerekli" >&2
rm -f "$ACCESS_TOKEN_FILE" "$REFRESH_TOKEN_FILE"
return 1
else
echo "HATA: Token yenileme basarisiz. HTTP $http_code" >&2
return 1
fi
}
Gerçek Dünya Senaryo 3: Client Credentials Flow
Kullanıcı müdahalesi olmadan servisler arası iletişimde (machine-to-machine) Client Credentials grant type kullanılır. Bir cron job’ın veya daemon’ın API’ye erişmesi gerektiğinde bu pattern’ı uygularsın:
#!/bin/bash
# Client Credentials ile token alma
get_service_token() {
local token_endpoint="https://auth.example.com/oauth/token"
# Credentials'ı environment variable'dan al, asla koda gömme
if [[ -z "$CLIENT_ID" ]] || [[ -z "$CLIENT_SECRET" ]]; then
echo "HATA: CLIENT_ID ve CLIENT_SECRET environment variable'lari tanimlanmamis" >&2
exit 1
fi
local response
response=$(curl -s -X POST "$token_endpoint"
-H "Content-Type: application/x-www-form-urlencoded"
--data-urlencode "grant_type=client_credentials"
--data-urlencode "client_id=${CLIENT_ID}"
--data-urlencode "client_secret=${CLIENT_SECRET}"
--data-urlencode "scope=read:users write:reports")
local access_token
access_token=$(echo "$response" | jq -r '.access_token')
if [[ "$access_token" == "null" ]] || [[ -z "$access_token" ]]; then
echo "HATA: Token alinamadi. Sunucu yaniti: $response" >&2
exit 1
fi
echo "$access_token"
}
# Kullanım
TOKEN=$(get_service_token)
# Scope'a göre yetkilendirilmiş istek
curl -s -X GET "https://api.example.com/v1/reports"
-H "Authorization: Bearer $TOKEN"
-H "Accept: application/json" | jq .
Güvenlik En İyi Pratikleri
Bearer Token kullanırken güvenliği ihmal etmek en büyük hataların başında gelir.
Token Saklama
- Token’ları asla kaynak koduna yazma
- Environment variable veya secret management sistemi kullan (HashiCorp Vault, AWS Secrets Manager)
- Log dosyalarına token yazdırmaktan kaçın
- Token’ları düz metin olarak diske yazma, şifreli store kullan
İletim Güvenliği
- Her zaman HTTPS kullan, HTTP üzerinden token göndermek kabul edilemez
- TLS sertifika doğrulamasını devre dışı bırakma (curl’de
-kflag’i production’da yasak) - Token’ı URL parametresi olarak değil, header’da gönder (URL’ler loglara düşer)
Token Ömrü ve İptal
- Access token’ları kısa tutulsun: 15-60 dakika ideal
- Kritik işlemler için token iptal mekanizması kur
- Şüpheli aktivite tespit edildiğinde tüm token’ları geçersiz kılabilecek yapı kur
# YANLIS: Token URL'de
curl "https://api.example.com/users?access_token=eyJhbGc..."
# DOGRU: Token header'da
curl -H "Authorization: Bearer eyJhbGc..." "https://api.example.com/users"
# YANLIS: TLS dogrulama devre disi
curl -k -H "Authorization: Bearer ..." "https://api.example.com/users"
# DOGRU: Özel CA sertifikasi kullanma
curl --cacert /etc/ssl/certs/company-ca.pem
-H "Authorization: Bearer ..."
"https://api.example.com/users"
Token Doğrulama ve Hata Yönetimi
API istemci tarafında hata durumlarını düzgün yönetmek şart:
#!/bin/bash
# Gelismis API istemci fonksiyonu
api_call() {
local method="$1"
local url="$2"
local data="${3:-}"
local max_retries=3
local retry_count=0
while [[ $retry_count -lt $max_retries ]]; do
local token
token=$(get_valid_token)
local response
local http_code
if [[ -n "$data" ]]; then
response=$(curl -s -w "n%{http_code}"
-X "$method" "$url"
-H "Authorization: Bearer $token"
-H "Content-Type: application/json"
-d "$data")
else
response=$(curl -s -w "n%{http_code}"
-X "$method" "$url"
-H "Authorization: Bearer $token")
fi
http_code=$(echo "$response" | tail -n1)
local body
body=$(echo "$response" | head -n-1)
case "$http_code" in
200|201|204)
echo "$body"
return 0
;;
401)
echo "Token gecersiz, yenileniyor..." >&2
invalidate_token
retry_count=$((retry_count + 1))
;;
403)
echo "HATA: Yetersiz yetki. Gerekli scope eksik olabilir" >&2
return 1
;;
429)
local retry_after
retry_after=$(echo "$response" | grep -i "retry-after" | awk '{print $2}' | tr -d 'r')
echo "Rate limit asimi. ${retry_after:-60} saniye bekleniyor..." >&2
sleep "${retry_after:-60}"
retry_count=$((retry_count + 1))
;;
5*)
echo "Sunucu hatasi: HTTP $http_code. Yeniden deneniyor..." >&2
sleep $((retry_count * 2))
retry_count=$((retry_count + 1))
;;
*)
echo "HATA: Beklenmeyen HTTP kodu: $http_code" >&2
echo "Yanit: $body" >&2
return 1
;;
esac
done
echo "HATA: Maksimum deneme sayisina ulasildi" >&2
return 1
}
Nginx ile Token Proxy Konfigürasyonu
Production ortamında sıkça karşılaşılan bir senaryo: Backend servisine gelen istekleri Nginx üzerinden yönlendirirken token eklemek veya doğrulamak:
# /etc/nginx/conf.d/api-proxy.conf
# Upstream token dogrulama servisi
upstream auth_service {
server auth.internal:8080;
}
upstream api_backend {
server api.internal:3000;
}
server {
listen 443 ssl;
server_name api.example.com;
# SSL konfigurasyonu
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
location /v1/ {
# Once auth servisine dogrulama istegi at
auth_request /auth/verify;
auth_request_set $user_id $upstream_http_x_user_id;
auth_request_set $user_scope $upstream_http_x_user_scope;
# Dogrulanmis kullanici bilgisini backend'e ilet
proxy_set_header X-User-ID $user_id;
proxy_set_header X-User-Scope $user_scope;
proxy_set_header Authorization $http_authorization;
proxy_pass http://api_backend;
}
# Token dogrulama internal endpoint'i
location = /auth/verify {
internal;
proxy_pass http://auth_service/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Authorization $http_authorization;
}
# 401 hatasi icin ozel yonlendirme
error_page 401 = @unauthorized;
location @unauthorized {
return 401 '{"error": "unauthorized", "message": "Gecerli bir Bearer token gerekli"}';
add_header Content-Type application/json;
}
}
Monitoring ve Token Güvenliği İzleme
Token bazlı sistemlerde anomali tespiti yapmak güvenlik açısından kritik:
#!/bin/bash
# Token kullanim anomali kontrolu
check_token_anomalies() {
local log_file="/var/log/nginx/api_access.log"
local alert_threshold=100 # Bir token'dan 5 dakikada max istek sayisi
local time_window=300 # 5 dakika
local current_time
current_time=$(date +%s)
local window_start=$((current_time - time_window))
# Son 5 dakikadaki token kullanim sayilarini hesapla
awk -v start="$window_start" '
{
# Bearer token'i logdan cikar
match($0, /Bearer ([A-Za-z0-9._-]+)/, arr)
if (arr[1] != "") {
# Sadece token'in ilk 20 karakterini kullan (guvenlik icin)
token = substr(arr[1], 1, 20)
count[token]++
}
}
END {
for (token in count) {
print count[token], token
}
}' "$log_file" | sort -rn | while read count token; do
if [[ "$count" -gt "$alert_threshold" ]]; then
echo "UYARI: Token ${token}... son 5 dakikada ${count} istek atti"
# Burada alerting sistemine (PagerDuty, Slack vb.) bildirim gonder
fi
done
}
# Her 5 dakikada bir calistir
check_token_anomalies
Sık Yapılan Hatalar
Hatalı Header Formatı: Bearer ile token arasında tek boşluk olmalı. Bearer token (çift boşluk) veya bearer token (küçük harf) bazı sunucularda sorun çıkarır.
Token’ı Log’a Düşürmek: Hata ayıklama sırasında set -x ile tüm komutları loglarken token’ların gözükmesi ciddi güvenlik açığıdır.
Expire Kontrolü Yapmamak: Token expire olduktan sonra gelen 401 hatalarını sadece yeniden token alarak çözmek yerine, önleyici expire kontrolü yap.
Tek Token Kullanmak: Farklı servisler ve ortamlar için farklı token’lar kullan. Bir token sızarsa hasarı minimize edersin.
Scope’ları Görmezden Gelmek: Token alırken sadece ihtiyacın olan scope’ları iste. Minimum yetki prensibi burada da geçerli.
Sonuç
Bearer Token, doğru uygulandığında hem güvenli hem de esnek bir kimlik doğrulama mekanizmasıdır. Özet olarak şunları aklında tut:
- Token’ları her zaman HTTPS üzerinden, header’da ilet
- Kısa ömürlü access token ve uzun ömürlü refresh token kombinasyonu kullan
- Credentials’ları asla koda gömme, environment variable veya secret manager kullan
- Hata durumlarını özellikle 401 ve 429’u düzgün yönet
- Token’larını loglardan koru, anomali tespiti yap
- Minimum yetki prensibini uygula, sadece gerekli scope’ları iste
Bu prensipleri uygulayan bir sistem kurduğunda, hem geliştiricilerin hayatını kolaylaştıran hem de güvenlik standartlarını karşılayan sağlam bir API entegrasyon altyapısına sahip olursun. Bearer Token mekanizması karmaşık değil, önemli olan tutarlı ve disiplinli uygulamak.
