OpenID Connect Nedir: OAuth 2.0 ile Kimlik Katmanı
Kimlik doğrulama dünyasında en çok karıştırılan konulardan biri OAuth 2.0 ile OpenID Connect arasındaki fark. Çoğu geliştirici ve sysadmin “OAuth ile login yapıyorum” diyor, ama aslında yaptıkları şey OpenID Connect. Bu ikisini birbirinden ayırt etmek, güvenli ve doğru çalışan bir sistem kurmanın ilk adımı. OAuth 2.0 bir yetkilendirme protokolü, yani “bu uygulama şu kaynağa erişebilir” sorusunu çözüyor. OpenID Connect ise bunun üstüne oturan bir kimlik katmanı, yani “bu kullanıcı kim?” sorusunu yanıtlıyor. Gelin bu ikisini, aralarındaki ilişkiyi ve gerçek dünya senaryolarında nasıl kullanıldığını inceleyelim.
OAuth 2.0’ı Tekrar Hatırlayalım
OAuth 2.0, bir uygulamanın kullanıcı adına başka bir servise erişmesini sağlayan yetkilendirme çerçevesi. Klasik örnek: bir uygulama Google Drive’ınıza erişmek istiyor. Siz Google’a gidip “evet, bu uygulamaya Drive erişimi ver” diyorsunuz. OAuth bu akışı yönetiyor.
Temel kavramlar şunlar:
- Resource Owner: Kaynağa sahip olan kullanıcı
- Client: Erişim isteyen uygulama
- Authorization Server: Token dağıtan sunucu (Google, GitHub, Keycloak vs.)
- Resource Server: API veya korunan kaynak
- Access Token: Kaynağa erişim için kullanılan token
- Refresh Token: Yeni access token almak için kullanılan uzun ömürlü token
OAuth 2.0’ın en büyük eksikliği şu: size bir access token veriyor ama bu token “kim” olduğunuzu söylemiyor. Sadece “bu token şu izinlere sahip” diyor. Kimlik doğrulama için başka bir katmana ihtiyaç var, işte OpenID Connect burada devreye giriyor.
OpenID Connect Nedir?
OpenID Connect (OIDC), OAuth 2.0 üzerine inşa edilmiş bir kimlik katmanı. OAuth 2.0’ın yetkilendirme akışını kullanıyor ama üstüne bir şey ekliyor: ID Token. Bu token, kullanıcının kim olduğunu kriptografik olarak doğrulanmış bir şekilde taşıyor.
OIDC’nin eklediği temel kavramlar:
- ID Token: JWT formatında, kullanıcı kimliğini içeren token
- UserInfo Endpoint: Kullanıcı bilgilerini döndüren standart endpoint
- Scope: openid: OAuth flow’a eklenen ve OIDC’yi aktive eden scope
- Claims: Token içindeki kullanıcı bilgileri (sub, email, name vs.)
- Discovery Document:
.well-known/openid-configurationendpoint’i
Kısaca: OAuth 2.0 ile bir kapıyı açıyorsunuz, OIDC ile o kapıdan geçen kişinin kimlik kartını inceliyorsunuz.
ID Token Yapısı
ID Token aslında bir JWT (JSON Web Token). Üç parçadan oluşuyor: header, payload ve signature. Payload kısmında claims denen bilgiler var.
Standart OIDC claims:
- sub: Subject identifier, kullanıcının benzersiz ID’si
- iss: Issuer, token’ı kim yayınladı
- aud: Audience, token kimin için
- exp: Expiration, token ne zaman sona eriyor
- iat: Issued at, token ne zaman oluşturuldu
- nonce: Replay attack’ları önlemek için
- email: Kullanıcının email adresi
- email_verified: Email doğrulanmış mı
- name: Kullanıcının tam adı
- given_name: İsim
- family_name: Soyisim
- picture: Profil fotoğrafı URL’i
Bir ID Token decode edelim:
# JWT token'ı decode etmek için jq kullanabiliriz
TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFobWV0IFlpbG1heiIsImVtYWlsIjoiYWhtZXRAZXhhbXBsZS5jb20iLCJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJhdWQiOiJteWFwcCIsImV4cCI6MTcwMDAwMDAwMCwiaWF0IjoxNjk5OTk2NDAwfQ.signature"
# Payload kısmını decode et (ikinci parça)
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .
# Çıktı:
# {
# "sub": "1234567890",
# "name": "Ahmet Yilmaz",
# "email": "[email protected]",
# "iss": "https://auth.example.com",
# "aud": "myapp",
# "exp": 1700000000,
# "iat": 1699996400
# }
Discovery Document
OIDC’nin en güzel özelliklerinden biri standardize edilmiş bir discovery endpoint’i olması. Bir identity provider’ın desteklediği her şeyi tek bir yerden öğrenebiliyorsunuz.
# Google'ın OIDC discovery document'ini çekelim
curl -s https://accounts.google.com/.well-known/openid-configuration | jq '{
issuer: .issuer,
authorization_endpoint: .authorization_endpoint,
token_endpoint: .token_endpoint,
userinfo_endpoint: .userinfo_endpoint,
jwks_uri: .jwks_uri,
scopes_supported: .scopes_supported
}'
# Keycloak için
curl -s https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration | jq .
Bu document size şunları söylüyor: authorization endpoint nerede, token endpoint nerede, hangi scope’lar destekleniyor, hangi signing algoritmaları kullanılıyor, UserInfo endpoint nerede.
OIDC Authorization Code Flow
En yaygın ve en güvenli akış Authorization Code Flow. Özellikle web uygulamaları için ideal. Adım adım nasıl çalıştığını görelim.
# Adım 1: Authorization isteği oluştur
# Kullanıcıyı şu URL'e yönlendir:
CLIENT_ID="myapp"
REDIRECT_URI="https://myapp.example.com/callback"
STATE=$(openssl rand -hex 16)
NONCE=$(openssl rand -hex 16)
SCOPE="openid email profile"
AUTH_ENDPOINT="https://auth.example.com/authorize"
AUTH_URL="${AUTH_ENDPOINT}?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}&state=${STATE}&nonce=${NONCE}"
echo "Kullanici su URL'e yonlendirilecek:"
echo $AUTH_URL
Kullanıcı giriş yapıp onayladıktan sonra authorization server, redirect_uri‘ye bir code parametresi ile yönlendiriyor. Bu code’u access token ve ID token ile değiştiriyorsunuz.
# Adım 2: Code'u token ile değiştir
CODE="authorization_code_buraya"
CLIENT_SECRET="supersecret"
TOKEN_ENDPOINT="https://auth.example.com/token"
curl -X POST $TOKEN_ENDPOINT
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=authorization_code"
-d "code=${CODE}"
-d "redirect_uri=${REDIRECT_URI}"
-d "client_id=${CLIENT_ID}"
-d "client_secret=${CLIENT_SECRET}"
# Yanıt:
# {
# "access_token": "eyJ...",
# "id_token": "eyJ...",
# "token_type": "Bearer",
# "expires_in": 3600,
# "refresh_token": "eyJ..."
# }
UserInfo Endpoint Kullanımı
Access token alındıktan sonra, daha fazla kullanıcı bilgisine ihtiyaç duyarsanız UserInfo endpoint’ini kullanabilirsiniz.
# Access token ile UserInfo endpoint'ini çağır
ACCESS_TOKEN="eyJhbGciOiJSUzI1NiJ9..."
USERINFO_ENDPOINT="https://auth.example.com/userinfo"
curl -H "Authorization: Bearer ${ACCESS_TOKEN}"
-H "Accept: application/json"
$USERINFO_ENDPOINT
# Yanıt:
# {
# "sub": "1234567890",
# "name": "Ahmet Yilmaz",
# "given_name": "Ahmet",
# "family_name": "Yilmaz",
# "email": "[email protected]",
# "email_verified": true,
# "picture": "https://example.com/photo.jpg",
# "locale": "tr-TR"
# }
Keycloak ile OIDC Kurulumu
Gerçek dünyada en çok kullanılan self-hosted identity provider Keycloak. Docker ile hızlıca ayağa kaldıralım.
# Keycloak'u Docker ile başlat
docker run -d
--name keycloak
-p 8080:8080
-e KEYCLOAK_ADMIN=admin
-e KEYCLOAK_ADMIN_PASSWORD=admin123
quay.io/keycloak/keycloak:23.0.0 start-dev
# Realm ve client oluşturmak için Keycloak Admin CLI kullanabiliriz
# Önce token alalım
ADMIN_TOKEN=$(curl -s -X POST
http://localhost:8080/realms/master/protocol/openid-connect/token
-H "Content-Type: application/x-www-form-urlencoded"
-d "username=admin"
-d "password=admin123"
-d "grant_type=password"
-d "client_id=admin-cli" | jq -r '.access_token')
# Yeni realm oluştur
curl -X POST http://localhost:8080/admin/realms
-H "Authorization: Bearer $ADMIN_TOKEN"
-H "Content-Type: application/json"
-d '{
"realm": "mycompany",
"enabled": true,
"displayName": "My Company"
}'
# Client oluştur
curl -X POST http://localhost:8080/admin/realms/mycompany/clients
-H "Authorization: Bearer $ADMIN_TOKEN"
-H "Content-Type: application/json"
-d '{
"clientId": "myapp",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"redirectUris": ["https://myapp.example.com/*"],
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false
}'
Python ile OIDC Entegrasyonu
Uygulamalarınıza OIDC entegrasyonu eklemek için Python’da authlib kütüphanesi çok kullanışlı.
# Bağımlılıkları yükle
pip install authlib flask requests
# Flask uygulaması için OIDC entegrasyonu
cat > app.py << 'EOF'
from flask import Flask, redirect, url_for, session, request, jsonify
from authlib.integrations.flask_client import OAuth
import os
app = Flask(__name__)
app.secret_key = os.urandom(24)
oauth = OAuth(app)
# OIDC provider kaydet
oauth.register(
name='myidp',
client_id='myapp',
client_secret='myclientsecret',
server_metadata_url='https://auth.example.com/.well-known/openid-configuration',
client_kwargs={
'scope': 'openid email profile'
}
)
@app.route('/login')
def login():
# Kullaniciyi IDP'ye yonlendir
redirect_uri = url_for('callback', _external=True)
return oauth.myidp.authorize_redirect(redirect_uri)
@app.route('/callback')
def callback():
# Token al ve dogrula
token = oauth.myidp.authorize_access_token()
# ID token otomatik dogrulanir
id_token = token.get('id_token')
userinfo = token.get('userinfo')
# Session'a kaydet
session['user'] = {
'sub': userinfo['sub'],
'email': userinfo.get('email'),
'name': userinfo.get('name')
}
return redirect(url_for('profile'))
@app.route('/profile')
def profile():
user = session.get('user')
if not user:
return redirect(url_for('login'))
return jsonify(user)
@app.route('/logout')
def logout():
session.clear()
# IDP'de de logout yap (backchannel logout)
return redirect('https://auth.example.com/logout?redirect_uri=https://myapp.example.com')
if __name__ == '__main__':
app.run(debug=True)
EOF
python app.py
Nginx ile OIDC Proxy Kurulumu
Mevcut uygulamaları değiştirmeden önlerine bir OIDC proxy koymak yaygın bir pattern. oauth2-proxy bu iş için mükemmel.
# oauth2-proxy kurulumu
wget https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.6.0/oauth2-proxy-v7.6.0.linux-amd64.tar.gz
tar xzf oauth2-proxy-v7.6.0.linux-amd64.tar.gz
sudo mv oauth2-proxy-v7.6.0.linux-amd64/oauth2-proxy /usr/local/bin/
# Keycloak ile kullanmak için konfigürasyon
cat > /etc/oauth2-proxy/oauth2-proxy.cfg << 'EOF'
provider = "oidc"
provider_display_name = "Keycloak"
oidc_issuer_url = "https://keycloak.example.com/realms/mycompany"
client_id = "oauth2-proxy"
client_secret = "proxy-secret-buraya"
cookie_secret = "zorlu-cookie-secret-32-byte"
cookie_secure = true
cookie_domain = ".example.com"
email_domains = ["example.com"]
upstreams = ["http://localhost:3000"]
redirect_url = "https://app.example.com/oauth2/callback"
http_address = "0.0.0.0:4180"
# ID token'dan claim'leri header olarak geç
pass_user_headers = true
set_xauthrequest = true
EOF
# Servisi başlat
oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg &
# Nginx konfigürasyonu
cat > /etc/nginx/sites-available/myapp << 'EOF'
server {
listen 443 ssl;
server_name app.example.com;
location /oauth2/ {
proxy_pass http://127.0.0.1:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location @error401 {
return 302 /oauth2/sign_in?rd=$scheme://$host$request_uri;
}
location / {
auth_request /oauth2/auth;
error_page 401 = @error401;
# Kullanıcı bilgilerini backend'e geç
auth_request_set $user $upstream_http_x_auth_request_user;
auth_request_set $email $upstream_http_x_auth_request_email;
proxy_set_header X-User $user;
proxy_set_header X-Email $email;
proxy_pass http://localhost:3000;
}
}
EOF
nginx -t && systemctl reload nginx
Token Doğrulama ve Güvenlik Kontrolleri
Token aldınız, güzel. Peki bunu doğru doğruluyor musunuz? Bu kısım çok kritik.
# JWKS endpoint'inden public key'leri çek
JWKS_URI="https://auth.example.com/.well-known/jwks.json"
curl -s $JWKS_URI | jq .
# Python ile token doğrulama scripti
cat > verify_token.py << 'EOF'
import jwt
import requests
from jwt.algorithms import RSAAlgorithm
import json
def get_public_keys(jwks_uri):
"""JWKS endpoint'inden public key'leri al"""
response = requests.get(jwks_uri)
jwks = response.json()
keys = {}
for key_data in jwks['keys']:
kid = key_data['kid']
public_key = RSAAlgorithm.from_jwk(json.dumps(key_data))
keys[kid] = public_key
return keys
def verify_id_token(token, client_id, issuer, jwks_uri):
"""ID Token'ı doğrula"""
# Token header'ından kid al
header = jwt.get_unverified_header(token)
kid = header.get('kid')
# Public key'leri al
public_keys = get_public_keys(jwks_uri)
if kid not in public_keys:
raise ValueError(f"Bilinmeyen kid: {kid}")
public_key = public_keys[kid]
# Token'ı doğrula - tüm kritik kontroller burada yapılıyor
claims = jwt.decode(
token,
public_key,
algorithms=['RS256'],
audience=client_id, # aud kontrolü
issuer=issuer, # iss kontrolü
options={
'verify_exp': True, # exp kontrolü
'verify_iat': True, # iat kontrolü
}
)
return claims
# Kullanım
try:
claims = verify_id_token(
token="eyJ...",
client_id="myapp",
issuer="https://auth.example.com",
jwks_uri="https://auth.example.com/.well-known/jwks.json"
)
print(f"Token gecerli. Kullanici: {claims['email']}")
except jwt.ExpiredSignatureError:
print("Token suresi dolmus!")
except jwt.InvalidAudienceError:
print("Token bu uygulama icin degil!")
except Exception as e:
print(f"Token gecersiz: {e}")
EOF
python3 verify_token.py
Gerçek Dünya Senaryosu: Microservice Mimarisi
Bir e-ticaret şirketinde çalıştığınızı düşünün. Onlarca microservice var: ürün servisi, sipariş servisi, ödeme servisi vs. Her servisin kullanıcı kimliğini doğrulaması gerekiyor.
Yanlış yaklaşım: Her servis kendi login sayfasına sahip, her biri ayrı kullanıcı veritabanı tutuyor. Kullanıcı her servise ayrı login yapıyor.
Doğru yaklaşım: Merkezi bir identity provider (Keycloak), tüm servisler OIDC ile entegre.
Akış şöyle işliyor:
- Kullanıcı frontend’e giriş yapıyor, Keycloak’tan ID token ve access token alıyor
- Frontend her API isteğinde access token’ı Authorization header’ında gönderiyor
- Her microservice token’ı doğruluyor, sub claim’inden kullanıcıyı tanıyor
- Servisler arası iletişimde Client Credentials flow kullanılıyor
# Servisler arası iletişim için Client Credentials Flow
# Bu flow'da kullanıcı yok, servis kendi kimliğiyle token alıyor
curl -X POST https://auth.example.com/token
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=client_credentials"
-d "client_id=order-service"
-d "client_secret=order-service-secret"
-d "scope=product-service:read"
# Alınan token ile product service'i çağır
SERVICE_TOKEN="eyJ..."
curl -H "Authorization: Bearer $SERVICE_TOKEN"
https://product-service.internal/api/products/123
Bu mimaride önemli noktalar:
- Token cache: Her istek için token almak yerine, token expire olmadan önce yeniliyorsunuz
- Scope kontrolü: Her servis sadece ihtiyacı olan scope’ları talep ediyor
- Token introspection: Bazı durumlarda token’ı authorization server’a doğrulatmak gerekebilir (opaque token kullanıyorsanız)
- Backchannel logout: Kullanıcı çıkış yaptığında tüm servislerin bilgilenmesi
Yaygın Hatalar ve Çözümleri
Sysadmin olarak bu hataları kaçınılmaz görürsünüz:
Nonce doğrulamasını atlama: Authorization isteğinde gönderdiğiniz nonce’u, ID token’dan validate etmeyi unutmak replay attack’lara kapı açar.
State parametresini kontrol etmeme: CSRF saldırılarına karşı state parametresini mutlaka doğrulayın.
Token’ı sadece decode edip doğrulamamak: Token’ı decode etmek ve doğrulamak çok farklı şey. İmza, exp, iss, aud kontrollerinin hepsini yapın.
Clock skew problemleri: Token exp kontrolünde sunucular arası saat farkı sorun çıkarır. Az da olsa bir tolerans (genellikle 5 dakika) bırakın.
HTTP üzerinden OIDC: Token’lar hassas bilgi. HTTPS zorunlu, istisnasız.
Sonuç
OpenID Connect, modern kimlik doğrulama dünyasının omurgası haline geldi. OAuth 2.0’ın yetkilendirme gücünü alıp üstüne standartlaştırılmış bir kimlik katmanı ekliyor. Tek Sign-On (SSO) kurmak istiyorsanız, microservice mimarisinde merkezi kimlik yönetimi yapacaksanız, ya da “Google ile giriş yap” özelliği ekleyecekseniz, OIDC öğrenmek zorundasınız.
Keycloak gibi self-hosted çözümler kurumsal ortamlar için mükemmel başlangıç noktası. Production’a çıkmadan önce discovery document’ini inceleyin, token doğrulama mantığını sıkı tutun, nonce ve state parametrelerini asla atlamayın. JWKS rotation’ı da göz ardı etmeyin, public key’ler değişebilir ve uygulamanız buna adapte olabilmeli.
Kimlik doğrulama her zaman “şimdilik çalışıyor, sonra düzeltirim” yaklaşımıyla yapılacak bir şey değil. Temeli sağlam atmak, ileride saatlerce debug süresini ve potansiyel güvenlik açıklarını önlüyor.
