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 -k flag’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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir