OAuth 2.0 Akış Türleri: Authorization Code, Implicit ve Client Credentials

Modern API entegrasyonlarında kimlik doğrulama ve yetkilendirme konusu, sysadmin olarak en çok baş ağrıtan konulardan biri. Bir servisi başka bir servisle konuşturmaya çalışıyorsun, token süresi doluyor, refresh mekanizması çalışmıyor, kullanıcılar “401 Unauthorized” hatası alıyor… Bu senaryolar çok tanıdık geliyor olmalı. OAuth 2.0, bu karmaşayı standartlaştırmak için geliştirilmiş bir yetkilendirme çerçevesi ve hangi akış türünü ne zaman kullanacağını bilmek, hem güvenlik hem de operasyonel istikrar açısından kritik önem taşıyor.

OAuth 2.0 Nedir ve Neden Önemli?

OAuth 2.0’ı basitçe şöyle açıklayabiliriz: Bir uygulamanın, kullanıcı adı ve şifreyi paylaşmadan başka bir uygulamadaki kaynağa erişmesine izin veren bir yetkilendirme protokolü. “Authorization” kelimesinin altını çizmek gerekiyor, çünkü OAuth 2.0 bir kimlik doğrulama (authentication) protokolü değil, yetkilendirme (authorization) protokolüdür. Kimlik doğrulama için OpenID Connect, OAuth 2.0’ın üzerine inşa edilmiş ayrı bir katmandır.

Gerçek dünya senaryosu olarak düşün: Şirketinin muhasebe uygulaması, çalışanların Google Drive’ındaki faturalara erişmek istiyor. Bunu yapabilmek için iki yol var:

  • Çalışanın Google şifresini uygulamaya girmesi (berbat fikir)
  • OAuth 2.0 ile sınırlı yetkili bir erişim token’ı alması (doğru yol)

OAuth 2.0 ekosisteminde dört temel rol var:

  • Resource Owner: Kaynağın sahibi, genellikle son kullanıcı
  • Client: Kaynağa erişmek isteyen uygulama
  • Authorization Server: Token’ları veren sunucu (Keycloak, Auth0, Okta gibi)
  • Resource Server: Korunan kaynakları barındıran API

Şimdi bu ekosistemde kullanılan üç temel akış türünü inceleyelim.

Authorization Code Flow

Authorization Code Flow, OAuth 2.0’ın en güvenli ve en yaygın kullanılan akış türüdür. Web uygulamaları ve mobil uygulamalar için tasarlanmıştır. Temel prensip şu: Client, token’ı doğrudan almaz. Önce bir “authorization code” alır, bu kodu sunucu tarafında token ile değiştirir. Bu sayede token hiçbir zaman tarayıcı geçmişine veya HTTP loglarına düşmez.

Adım Adım Akış

1. Authorization Request

Kullanıcı uygulamana gelir ve “Google ile Giriş Yap” butonuna tıklar. Uygulaman kullanıcıyı authorization server’a yönlendirir:

# Authorization URL oluşturma
AUTH_URL="https://auth.example.com/oauth2/authorize"
CLIENT_ID="myapp_client_id"
REDIRECT_URI="https://myapp.example.com/callback"
STATE=$(openssl rand -hex 16)  # CSRF koruması için
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '+=/' | cut -c1-43)  # PKCE için

echo "Authorization URL:"
echo "${AUTH_URL}?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=openid%20profile%20email&state=${STATE}"

2. Authorization Code ile Token Değişimi

Kullanıcı izin verirse, authorization server kullanıcıyı redirect_uri‘ye bir kod ile gönderir. Bu kodu sunucu tarafında token ile değiştirirsin:

# Authorization Code ile Access Token alma
curl -X POST https://auth.example.com/oauth2/token 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=authorization_code" 
  -d "code=AUTHORIZATION_CODE_BURAYA" 
  -d "redirect_uri=https://myapp.example.com/callback" 
  -d "client_id=myapp_client_id" 
  -d "client_secret=myapp_client_secret"

Başarılı yanıt şu formatta gelir:

# Beklenen yanıt
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200abc...",
  "scope": "openid profile email"
}

3. Token Yenileme

Access token süresi dolduğunda, refresh token kullanarak yeni bir token alabilirsin. Kullanıcının tekrar giriş yapmasına gerek yok:

# Refresh Token ile yeni Access Token alma
curl -X POST https://auth.example.com/oauth2/token 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=refresh_token" 
  -d "refresh_token=def50200abc..." 
  -d "client_id=myapp_client_id" 
  -d "client_secret=myapp_client_secret"

PKCE ile Authorization Code Flow

Mobil uygulamalar ve Single Page Applications (SPA) için client_secret güvenli şekilde saklanamaz. PKCE (Proof Key for Code Exchange) bu sorunu çözer:

# PKCE Code Verifier ve Challenge oluşturma
CODE_VERIFIER=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 64)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')

echo "Code Verifier: $CODE_VERIFIER"
echo "Code Challenge: $CODE_CHALLENGE"

# PKCE ile token isteği
curl -X POST https://auth.example.com/oauth2/token 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=authorization_code" 
  -d "code=AUTHORIZATION_CODE" 
  -d "redirect_uri=https://myapp.example.com/callback" 
  -d "client_id=myapp_client_id" 
  -d "code_verifier=$CODE_VERIFIER"

Authorization Code Flow Ne Zaman Kullanılır?

  • Web uygulamalarında kullanıcı etkileşimi gerektiren senaryolarda
  • Client secret’ı güvenli şekilde saklayabildiğinde
  • Refresh token desteği gereken uzun süreli oturumlarda
  • SPA ve mobil uygulamalarda (PKCE ile birlikte)

Implicit Flow

Implicit Flow, Authorization Code Flow’un basitleştirilmiş bir versiyonudur. Tarihsel olarak JavaScript tabanlı uygulamalar ve browserda çalışan SPA’lar için tasarlanmıştır. Temel farkı şu: Access token, authorization code olmadan doğrudan URL fragment’ı içinde döner.

Akış Nasıl Çalışır?

# Implicit Flow Authorization URL
# response_type=token dikkat et, code değil!
AUTH_URL="https://auth.example.com/oauth2/authorize"

echo "Implicit Flow URL:"
echo "${AUTH_URL}?response_type=token&client_id=myapp_client_id&redirect_uri=https://myapp.example.com/callback&scope=openid%20profile&state=$(openssl rand -hex 16)"

# Yanıt URL şu formatta döner:
# https://myapp.example.com/callback#access_token=TOKEN&token_type=bearer&expires_in=3600

Implicit Flow’un Sorunları

Şunu açıkça söylemek gerekiyor: Implicit Flow artık önerilmiyor. OAuth 2.0 Security Best Current Practice (BCP) belgesi bu akışı deprecated olarak işaretlemiş durumda. Neden?

  • Token URL fragment’ında görünür: Tarayıcı geçmişine düşebilir, proxy loglarına kaydedilebilir
  • Refresh token yok: Her oturum için kullanıcının yeniden giriş yapması gerekir
  • Token substitution saldırıları: Farklı bir uygulamanın token’ı inject etme riski
  • Referrer header sızıntısı: Access token, referrer header’ı aracılığıyla üçüncü taraflara sızabilir

Eğer hala Implicit Flow kullanan bir sistemin var, migration planı yapma vakti gelmiş demektir. Doğru alternatif PKCE ile Authorization Code Flow’dur.

Legacy Sistem Troubleshooting

Bazen eski sistemlerde Implicit Flow ile karşılaşırsın. Bu durumda token’ı debug etmek için:

# JWT Token decode etme (Implicit Flow ile alınan token)
ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiQWhtZXQgWWlsbWF6IiwiaWF0IjoxNjE2MjM5MDIyfQ.signature"

# Header ve payload'ı decode et
echo $ACCESS_TOKEN | cut -d'.' -f1 | base64 -d 2>/dev/null | python3 -m json.tool
echo $ACCESS_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool

# Token expiry kontrolü
PAYLOAD=$(echo $ACCESS_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null)
EXP=$(echo $PAYLOAD | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('exp', 'N/A'))")
echo "Token expires at: $(date -d @$EXP 2>/dev/null || date -r $EXP 2>/dev/null)"

Client Credentials Flow

Client Credentials Flow, machine-to-machine (M2M) iletişim için tasarlanmış akış türüdür. Burada kullanıcı yok, iki servis birbirleriyle konuşuyor. Microservice mimarisinde, cron job’larda, batch işlemlerinde ve API gateway’lerde en sık kullanılan OAuth akışıdır.

Temel Kullanım

# Client Credentials ile Access Token alma
CLIENT_ID="service-account-inventory"
CLIENT_SECRET="super-secret-key-buraya"
TOKEN_ENDPOINT="https://auth.example.com/oauth2/token"

TOKEN_RESPONSE=$(curl -s -X POST "$TOKEN_ENDPOINT" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=client_credentials" 
  -d "client_id=$CLIENT_ID" 
  -d "client_secret=$CLIENT_SECRET" 
  -d "scope=inventory:read inventory:write")

ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
EXPIRES_IN=$(echo $TOKEN_RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['expires_in'])")

echo "Token alındı, geçerlilik süresi: ${EXPIRES_IN} saniye"

Gerçek Dünya Senaryosu: Microservice Token Yönetimi

Diyelim ki stok servisi, sipariş servisiyle konuşmak istiyor. Bu senaryoda token’ı her istek için almak verimsiz, cache’lemek şart:

#!/bin/bash
# /usr/local/bin/get-service-token.sh
# Servis token'larını cache'leyen script

TOKEN_CACHE_DIR="/tmp/oauth_tokens"
mkdir -p "$TOKEN_CACHE_DIR"
chmod 700 "$TOKEN_CACHE_DIR"

CLIENT_ID="${1:-}"
CLIENT_SECRET="${2:-}"
SCOPE="${3:-}"
TOKEN_ENDPOINT="https://auth.example.com/oauth2/token"

CACHE_FILE="${TOKEN_CACHE_DIR}/${CLIENT_ID}.token"
CACHE_EXPIRY_FILE="${TOKEN_CACHE_DIR}/${CLIENT_ID}.expiry"

# Cache kontrolü: Token hala geçerli mi?
if [ -f "$CACHE_FILE" ] && [ -f "$CACHE_EXPIRY_FILE" ]; then
    EXPIRY=$(cat "$CACHE_EXPIRY_FILE")
    NOW=$(date +%s)
    # 60 saniyelik buffer bırak
    if [ $((EXPIRY - NOW)) -gt 60 ]; then
        cat "$CACHE_FILE"
        exit 0
    fi
fi

# Yeni token al
RESPONSE=$(curl -s -X POST "$TOKEN_ENDPOINT" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=client_credentials" 
  -d "client_id=$CLIENT_ID" 
  -d "client_secret=$CLIENT_SECRET" 
  -d "scope=$SCOPE")

ACCESS_TOKEN=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
EXPIRES_IN=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['expires_in'])")

# Cache'e yaz
echo -n "$ACCESS_TOKEN" > "$CACHE_FILE"
chmod 600 "$CACHE_FILE"
echo $(($(date +%s) + EXPIRES_IN)) > "$CACHE_EXPIRY_FILE"

echo -n "$ACCESS_TOKEN"

Bu script’i production’da şu şekilde kullanabilirsin:

# Servisi çağırırken token'ı al ve kullan
TOKEN=$(bash /usr/local/bin/get-service-token.sh 
  "service-inventory" 
  "client-secret-here" 
  "orders:read orders:write")

# API çağrısı yap
curl -s -X GET "https://api.example.com/orders" 
  -H "Authorization: Bearer $TOKEN" 
  -H "Content-Type: application/json"

Client Credentials ile Keycloak Entegrasyonu

Kurumsal ortamlarda Keycloak çok yaygın. İşte tipik bir Keycloak Client Credentials konfigürasyonu:

# Keycloak'ta client credentials flow testi
KEYCLOAK_URL="https://keycloak.company.com"
REALM="production"
CLIENT_ID="backend-service"
CLIENT_SECRET="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Token al
curl -s -X POST 
  "${KEYCLOAK_URL}/realms/${REALM}/protocol/openid-connect/token" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=client_credentials" 
  -d "client_id=${CLIENT_ID}" 
  -d "client_secret=${CLIENT_SECRET}" | python3 -m json.tool

# Token introspection - token geçerli mi kontrol et
ACCESS_TOKEN="TOKEN_BURAYA"
curl -s -X POST 
  "${KEYCLOAK_URL}/realms/${REALM}/protocol/openid-connect/token/introspect" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "token=${ACCESS_TOKEN}" 
  -d "client_id=${CLIENT_ID}" 
  -d "client_secret=${CLIENT_SECRET}" | python3 -m json.tool

Client Credentials Flow Ne Zaman Kullanılır?

  • Microservice’ler arası iletişimde
  • Cron job ve zamanlanmış görevlerde
  • CI/CD pipeline’larında API çağrılarında
  • Webhook handler’larında
  • Background worker servislerinde

Akış Türlerini Karşılaştırmak

Hangi akışı kullanacağına karar verirken şu kriterlere bak:

Authorization Code Flow kullan, eğer:

  • Bir kullanıcı var ve kullanıcı etkileşimi mümkün
  • Web uygulaması geliştiriyorsun
  • Client secret güvenle saklanabilir (server-side)
  • Uzun süreli oturum yönetimi gerekiyor

Authorization Code Flow + PKCE kullan, eğer:

  • Mobil uygulama geliştiriyorsun
  • SPA (React, Angular, Vue) yapıyorsun
  • Client secret saklayamazsın

Client Credentials Flow kullan, eğer:

  • İki servis birbiriyle konuşuyor, kullanıcı yok
  • Daemon, cron job veya batch iş var
  • Service account mantığıyla çalışıyorsun

Implicit Flow’dan kaçın:

  • Yeni proje başlatıyorsan kesinlikle kullanma
  • Mevcut sistemde varsa PKCE’ye migration planla

Güvenlik Notları ve Yaygın Hatalar

Production ortamında sıklıkla gördüğüm hatalar:

1. Client Secret’ı kodun içine gömmek

# YANLIS - asla yapma
CLIENT_SECRET="hardcoded-secret-in-code"

# DOGRU - environment variable veya secret manager kullan
CLIENT_SECRET="${OAUTH_CLIENT_SECRET}"
# veya
CLIENT_SECRET=$(vault kv get -field=client_secret secret/myapp/oauth)

2. State parametresini atlamak

Authorization Code Flow’da state parametresi CSRF saldırılarına karşı koruma sağlar. Atlanmamalı:

# State üretme ve doğrulama
# İstek öncesi
STATE=$(openssl rand -hex 32)
# Session'a kaydet: session["oauth_state"] = STATE

# Callback'te doğrulama
RECEIVED_STATE="$1"  # URL'den gelen state
STORED_STATE=$(get_from_session "oauth_state")

if [ "$RECEIVED_STATE" != "$STORED_STATE" ]; then
    echo "CSRF saldırısı tespit edildi! State uyuşmuyor."
    exit 1
fi

3. Token’ı güvensiz yerde saklamak

  • Tarayıcı localStorage’a access token koyma, sessionStorage kullan veya HttpOnly cookie tercih et
  • Server tarafında token’ları memory’de veya Redis gibi güvenli cache’de tut
  • Log dosyalarına token basma, her zaman mask’le

4. Token expiry’yi kontrol etmemek

# Token expiry kontrolü için basit fonksiyon
check_token_expiry() {
    local token="$1"
    local payload=$(echo "$token" | cut -d'.' -f2 | base64 -d 2>/dev/null)
    local exp=$(echo "$payload" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('exp', 0))" 2>/dev/null)
    local now=$(date +%s)
    
    if [ $((exp - now)) -lt 300 ]; then
        echo "UYARI: Token 5 dakika icinde surecek veya surdu!"
        return 1
    fi
    return 0
}

Monitoring ve Troubleshooting

Production’da OAuth sorunlarını hızlıca tespit etmek için loglama şart:

# OAuth endpoint'lerini test eden basit health check scripti
#!/bin/bash
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
TOKEN_ENDPOINT="https://auth.example.com/oauth2/token"

HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" 
  -X POST "$TOKEN_ENDPOINT" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=client_credentials" 
  -d "client_id=$CLIENT_ID" 
  -d "client_secret=$CLIENT_SECRET")

if [ "$HTTP_STATUS" -eq 200 ]; then
    echo "[$TIMESTAMP] OK - Authorization server saglikli (HTTP $HTTP_STATUS)"
else
    echo "[$TIMESTAMP] HATA - Authorization server yanit vermiyor (HTTP $HTTP_STATUS)"
    # Alert gonder
    curl -s -X POST "$SLACK_WEBHOOK" 
      -H "Content-Type: application/json" 
      -d "{"text":"OAuth Token Endpoint HATA: HTTP $HTTP_STATUS"}"
fi

Bu scripti cron’a ekleyerek periyodik izleme yapabilirsin:

# /etc/cron.d/oauth-healthcheck
*/5 * * * * root /usr/local/bin/oauth-healthcheck.sh >> /var/log/oauth-health.log 2>&1

Sonuç

OAuth 2.0 akış türleri ilk bakışta karmaşık görünebilir ama doğru akışı seçmek aslında birkaç soruya cevap vermekle başlıyor: Kullanıcı var mı? Uygulama client secret’ı güvenle saklayabiliyor mu? Machine-to-machine mi, yoksa kullanıcı odaklı bir akış mı?

Authorization Code Flow, kullanıcı tabanlı uygulamalar için altın standart. PKCE eklenince SPA ve mobil için de mükemmel hale geliyor. Client Credentials, microservice dünyasının vazgeçilmezi. Implicit Flow ise artık tarih olmalı, hala kullananlar için en kısa sürede PKCE migration’ı planlamak gerekiyor.

Sysadmin olarak bu akışları anlamak, sadece “neden 401 alıyorum?” sorusunu çözmekten ibaret değil. Token ömürleri, refresh mekanizmaları, secret yönetimi ve monitoring konularında bilinçli kararlar vermeni sağlıyor. Authorization server’ını doğru konfigüre etmek, scope’ları minimal tutmak ve token’ları güvenli saklamak, güvenli bir API ekosisteminin temel taşları. Bu konuyu ne kadar erken oturursa, ilerleyen dönemde o kadar az auth kaynaklı 3 AM alarm telefonu alırsın.

Bir yanıt yazın

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