SSO Nedir: Single Sign-On OAuth 2.0 ile Uygulama
Kullanıcıların her uygulama için ayrı ayrı şifre hatırlamak zorunda kaldığı günler geride kalıyor. Bir şirkette 10-15 farklı iç uygulama varsa ve her biri için kullanıcıların ayrı giriş yapması gerekiyorsa, bu hem kullanıcı deneyimini mahveder hem de güvenlik açısından kabus olur. İşte tam bu noktada SSO devreye giriyor. Single Sign-On, kullanıcının bir kez kimlik doğrulaması yapmasını ve bu doğrulamanın birden fazla uygulamada geçerli olmasını sağlayan bir mekanizma. OAuth 2.0 ise bu mekanizmanın modern web dünyasındaki en yaygın implementasyonu haline geldi.
SSO’nun Temel Mantığı
SSO’yu anlamak için önce klasik authentication sorununu anlamak gerekiyor. Diyelim ki şirketinizde bir HR uygulaması, bir proje yönetim aracı ve bir müşteri portal sistemi var. Klasik yaklaşımda her uygulama kendi kullanıcı veritabanını tutar, kendi session’larını yönetir. Bir kullanıcı şirketten ayrıldığında 3 ayrı sistemden kaldırmanız gerekir. Birini atlarsanız güvenlik açığı oluşur.
SSO’da ise merkezi bir Identity Provider (IdP) vardır. Tüm uygulamalar (bunlara Service Provider ya da Relying Party denir) kimlik doğrulamasını bu merkezi noktaya devreder. Kullanıcı bir kez giriş yapar, IdP bir token üretir, diğer uygulamalar bu token’ı kabul eder. Kullanıcı hesabı kapatıldığında sadece IdP’de kapatılır, tüm uygulamalar etkilenir.
OAuth 2.0 bu sistemin yetkilendirme (authorization) katmanını oluşturur. OpenID Connect (OIDC) ise OAuth 2.0’ın üzerine kimlik doğrulama (authentication) katmanını ekler. Pratikte SSO implementasyonlarının büyük çoğunluğu OAuth 2.0 + OIDC kombinasyonunu kullanır.
OAuth 2.0 Akışını Anlamak
OAuth 2.0’da birkaç farklı akış (flow) vardır ama SSO için en çok kullanılan Authorization Code Flow ile Client Credentials Flow‘dur.
Authorization Code Flow şu şekilde işler:
- Kullanıcı uygulamaya erişmeye çalışır
- Uygulama kullanıcıyı IdP’ye yönlendirir
- Kullanıcı IdP’de kimlik doğrulamasını yapar
- IdP, uygulamaya bir authorization code döner
- Uygulama bu code’u kullanarak access token alır
- Access token ile korumalı kaynaklara erişilir
Basit bir curl örneğiyle authorization code isteğinin nasıl başlatıldığına bakalım:
# Authorization endpoint'ine yönlendirme URL'ini oluşturma
curl -v "https://auth.sirketim.com/oauth/authorize?
response_type=code&
client_id=myapp-client-id&
redirect_uri=https://myapp.sirketim.com/callback&
scope=openid%20profile%20email&
state=random-csrf-token-xyz789"
Kullanıcı giriş yaptıktan sonra IdP, redirect_uri‘ye şöyle bir istek döner:
# IdP'nin döndürdüğü callback URL örneği
# https://myapp.sirketim.com/callback?code=AUTH_CODE_ABC&state=random-csrf-token-xyz789
# Bu code'u access token ile değiştirme
curl -X POST "https://auth.sirketim.com/oauth/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=authorization_code"
-d "code=AUTH_CODE_ABC"
-d "redirect_uri=https://myapp.sirketim.com/callback"
-d "client_id=myapp-client-id"
-d "client_secret=super-secret-value"
Keycloak ile Gerçek Dünya SSO Kurulumu
Teoriden pratiğe geçelim. Open source dünyasında SSO için en yaygın kullanılan çözüm Keycloak. Red Hat tarafından geliştirilen bu araç, production ortamlarında gerçekten güvenilir bir performans sergiliyor. Docker ile hızlıca ayağa kaldıralım:
# Keycloak'u Docker ile başlatma
docker run -d
--name keycloak
-p 8080:8080
-e KEYCLOAK_ADMIN=admin
-e KEYCLOAK_ADMIN_PASSWORD=admin123
-e KC_DB=postgres
-e KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak
-e KC_DB_USERNAME=keycloak
-e KC_DB_PASSWORD=keycloak_db_pass
--network sso-network
quay.io/keycloak/keycloak:23.0 start-dev
# Admin CLI ile realm oluşturma
docker exec -it keycloak /opt/keycloak/bin/kcadm.sh config credentials
--server http://localhost:8080
--realm master
--user admin
--password admin123
docker exec -it keycloak /opt/keycloak/bin/kcadm.sh create realms
-s realm=sirketim
-s enabled=true
-s displayName="Sirketim SSO"
Realm oluşturduktan sonra bir client (uygulama) tanımlamamız gerekiyor:
# Keycloak'ta client oluşturma
docker exec -it keycloak /opt/keycloak/bin/kcadm.sh create clients
-r sirketim
-s clientId=hr-application
-s enabled=true
-s publicClient=false
-s "redirectUris=["https://hr.sirketim.com/*"]"
-s "webOrigins=["https://hr.sirketim.com"]"
-s standardFlowEnabled=true
-s serviceAccountsEnabled=false
# Client secret'ı görüntüleme
docker exec -it keycloak /opt/keycloak/bin/kcadm.sh get clients
-r sirketim
-q clientId=hr-application
--fields id,secret
Token Doğrulama ve Introspection
Access token aldıktan sonra backend servislerinin bu token’ı doğrulaması gerekiyor. İki yöntem var: local validation (JWT token’ı kendi başına doğrulama) ve introspection (IdP’ye sorup doğrulama).
Local validation daha hızlıdır ama token revoke edildiğinde haberin olmayabilir. Introspection her seferinde network isteği atar ama güncel bilgi verir. Kritik sistemlerde introspection tercih edilir:
# Token introspection endpoint'i kullanma
ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -X POST "https://auth.sirketim.com/realms/sirketim/protocol/openid-connect/token/introspect"
-H "Content-Type: application/x-www-form-urlencoded"
-u "hr-application:client-secret-buraya"
-d "token=$ACCESS_TOKEN"
# Başarılı yanıt örneği (active: true olmalı)
# {
# "active": true,
# "sub": "user-uuid-123",
# "email": "[email protected]",
# "realm_access": { "roles": ["hr-manager"] },
# "exp": 1735000000
# }
JWT token’ı local olarak doğrulamak için JWKS endpoint’inden public key alınır:
# JWKS endpoint'inden public key alma
curl "https://auth.sirketim.com/realms/sirketim/protocol/openid-connect/certs"
# JWT token'ı decode ederek içeriğini görme (doğrulama yapmadan)
echo $ACCESS_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool
# jwt-cli aracı ile token doğrulama
# jwt-cli kurulumu: cargo install jwt-cli
jwt decode --secret @public_key.pem $ACCESS_TOKEN
Nginx ile SSO Proxy Kurulumu
Bir senaryo düşünelim: Eski bir legacy uygulama var ve bu uygulamaya OAuth 2.0 desteği ekleyemiyorsunuz. Nginx + oauth2-proxy kombinasyonuyla bu uygulamanın önüne SSO koyabilirsiniz:
# 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/
# oauth2-proxy konfigürasyonu
cat > /etc/oauth2-proxy/oauth2-proxy.cfg << 'EOF'
provider = "oidc"
oidc_issuer_url = "https://auth.sirketim.com/realms/sirketim"
client_id = "legacy-app-proxy"
client_secret = "proxy-client-secret"
cookie_secret = "super-random-32-char-secret-here"
cookie_secure = true
cookie_domains = [".sirketim.com"]
email_domains = ["sirketim.com"]
upstreams = ["http://localhost:8090"]
http_address = "0.0.0.0:4180"
redirect_url = "https://legacy.sirketim.com/oauth2/callback"
session_store_type = "redis"
redis_connection_url = "redis://localhost:6379"
skip_provider_button = true
pass_access_token = true
pass_user_headers = true
set_xauthrequest = true
EOF
# Servisi başlatma
oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg &
# Nginx konfigürasyonu
cat > /etc/nginx/sites-available/legacy-app << 'EOF'
server {
listen 443 ssl;
server_name legacy.sirketim.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 / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
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:8090;
}
}
EOF
Single Logout (SLO) Mekanizması
SSO’nun genellikle gözden kaçan ama kritik öneme sahip bir parçası: Single Logout. Kullanıcı çıkış yaptığında sadece mevcut uygulamadan çıkması yeterli değil, tüm oturumların sonlandırılması gerekiyor. Özellikle banka veya sağlık uygulamalarında bu hayati önem taşıyor.
Keycloak back-channel logout’u destekler. Bu mekanizmada kullanıcı bir uygulamadan çıkış yaptığında, Keycloak diğer tüm uygulamaların logout endpoint’lerine POST isteği atar:
# Kullanıcı oturumunu Keycloak admin API ile sonlandırma
ADMIN_TOKEN=$(curl -s -X POST
"https://auth.sirketim.com/realms/master/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=client_credentials"
-d "client_id=admin-cli"
-d "client_secret=admin-cli-secret" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# Belirli bir kullanıcının tüm oturumlarını görme
curl -H "Authorization: Bearer $ADMIN_TOKEN"
"https://auth.sirketim.com/admin/realms/sirketim/[email protected]"
| python3 -c "import sys,json; users=json.load(sys.stdin); print(users[0]['id'])"
# Kullanıcının tüm oturumlarını sonlandırma
USER_ID="kullanici-uuid-buraya"
curl -X POST
-H "Authorization: Bearer $ADMIN_TOKEN"
"https://auth.sirketim.com/admin/realms/sirketim/users/$USER_ID/logout"
Token Refresh ve Session Yönetimi
Access token’ların genellikle kısa ömürlü olması güvenlik açısından önemlidir. Tipik değerler 5-15 dakikadır. Kullanıcının her 5 dakikada bir tekrar giriş yapmasını istemiyorsanız refresh token mekanizmasını doğru kurmanız gerekiyor:
# Refresh token ile yeni access token alma
REFRESH_TOKEN="eyJhbGciOiJIUzI1NiIsInR..."
curl -X POST "https://auth.sirketim.com/realms/sirketim/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=refresh_token"
-d "client_id=hr-application"
-d "client_secret=client-secret-buraya"
-d "refresh_token=$REFRESH_TOKEN"
# Bash script ile otomatik token yenileme
#!/bin/bash
refresh_access_token() {
local refresh_token=$1
local response=$(curl -s -X POST
"https://auth.sirketim.com/realms/sirketim/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=refresh_token"
-d "client_id=api-service"
-d "client_secret=$CLIENT_SECRET"
-d "refresh_token=$refresh_token")
if echo "$response" | python3 -c "import sys,json; data=json.load(sys.stdin); exit(0 if 'access_token' in data else 1)"; then
echo "$response" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])"
else
echo "ERROR: Token refresh basarisiz" >&2
echo "$response" >&2
return 1
fi
}
Güvenlik Hardening
SSO kurarken güvenliği sıkılaştırmak için dikkat edilmesi gereken birkaç kritik nokta var:
PKCE (Proof Key for Code Exchange) kullanımı artık zorunlu sayılmalı. Özellikle SPA (Single Page Application) ve mobil uygulamalarda client secret saklayamadığınız için PKCE olmadan authorization code flow güvensizdir:
# PKCE için code_verifier ve code_challenge oluşturma
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-43)
echo "Code Verifier: $CODE_VERIFIER"
# SHA256 hash alıp base64url encode etme
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr -d '=' | tr '+/' '-_')
echo "Code Challenge: $CODE_CHALLENGE"
# PKCE ile authorization isteği
curl -v "https://auth.sirketim.com/realms/sirketim/protocol/openid-connect/auth?
response_type=code&
client_id=spa-application&
redirect_uri=https://spa.sirketim.com/callback&
scope=openid%20profile%20email&
state=$(openssl rand -hex 16)&
code_challenge=$CODE_CHALLENGE&
code_challenge_method=S256"
# Token alırken code_verifier gönderme
curl -X POST "https://auth.sirketim.com/realms/sirketim/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=authorization_code"
-d "code=AUTH_CODE_BURAYA"
-d "redirect_uri=https://spa.sirketim.com/callback"
-d "client_id=spa-application"
-d "code_verifier=$CODE_VERIFIER"
Bunların yanında dikkat edilmesi gereken diğer güvenlik noktaları:
- State parametresi: CSRF saldırılarını önler, her istekte random ve tek kullanımlık olmalı
- Redirect URI whitelist: Keycloak’ta izin verilen redirect URI’ları wildcard yerine açıkça tanımlanmalı
- Token expiry: Access token 15 dakikayı, refresh token ise 8 saati geçmemeli (kurumun politikasına göre ayarlanabilir)
- HTTPS zorunluluğu: OAuth 2.0 akışı hiçbir zaman HTTP üzerinden yapılmamalı
- Audience claim doğrulama: Token’daki
audalanı doğrulanmazsa token hijacking saldırısına açık olursunuz - Client secret rotasyonu: Keycloak admin API ile periyodik olarak client secret’lar rotate edilmeli
Monitoring ve Troubleshooting
SSO sisteminizin sağlığını izlemek için birkaç temel komut:
# Keycloak health endpoint kontrolü
curl -s "https://auth.sirketim.com/health/ready" | python3 -m json.tool
curl -s "https://auth.sirketim.com/health/live" | python3 -m json.tool
# Keycloak metrics (Prometheus formatında)
curl -s "https://auth.sirketim.com/metrics" | grep -E "^keycloak_"
# Aktif oturum sayısını kontrol etme
curl -H "Authorization: Bearer $ADMIN_TOKEN"
"https://auth.sirketim.com/admin/realms/sirketim/sessions/stats"
# Realm istatistiklerini görme
curl -H "Authorization: Bearer $ADMIN_TOKEN"
"https://auth.sirketim.com/admin/realms/sirketim"
| python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Users: {d.get("usersCount", 0)}')"
# Hatalı giriş denemelerini loglarda takip etme
docker logs keycloak 2>&1 | grep -i "login error|invalid credentials|brute force" | tail -50
# Token endpoint'inin response time'ını ölçme
time curl -s -X POST
"https://auth.sirketim.com/realms/sirketim/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=client_credentials"
-d "client_id=monitoring-client"
-d "client_secret=monitoring-secret" > /dev/null
Yaygın Hatalar ve Çözümleri
SSO kurulumlarında en sık karşılaşılan sorunlar:
- Clock skew: JWT token’larda
iatveexpclaim’leri sistem saatiyle doğrulanır. Sunucular arasında saat farkı varsa token geçerli olmasına rağmen reddedilebilir. Tüm sunucularda NTP kullanın, 30 saniyelik tolerans normal ama 5 dakikayı geçerse sorun çıkar - Redirect URI mismatch: Uygulamadan gelen
redirect_uriKeycloak’ta kayıtlı olan ile birebir eşleşmeli. Trailing slash bile fark yaratır - CORS hataları: SPA uygulamalarında token endpoint’ine direkt browser’dan istek atılırsa CORS bloğu yaşanır. Token exchange işlemleri backend’den yapılmalı
- Session fixation: Login öncesi ve sonrası session ID değişmiyorsa güvenlik açığı var demektir
- Refresh token leakage: Refresh token’lar sadece server-side storage’da tutulmalı, cookie veya localStorage’da saklanmamalı
Sonuç
SSO ve OAuth 2.0 implementasyonu ilk bakışta karmaşık görünse de mantığı kavrandıktan sonra oldukça sistematik bir yapıya oturuyor. Keycloak gibi olgun bir IdP kullanıyorsanız işin büyük kısmı konfigürasyona geliyor. Önemli olan birkaç temel prensibi asla unutmamak: HTTPS zorunlu, PKCE kullan, state parametresini atlatma, token sürelerini kısa tut ve logout mekanizmasını test et.
Bir şirkette SSO’yu ilk kez kuruyorsanız küçük başlayın. İki ya da üç uygulamayı entegre edin, sistemi izleyin, sonra diğerlerini ekleyin. Big bang yaklaşımıyla 20 uygulamayı aynı anda taşımaya çalışmak büyük sorunlara yol açar. Kullanıcı etkisini minimize etmek için de migration sırasında eski authentication sistemini bir süre paralel çalıştırın.
Son olarak, SSO bir gümüş kurşun değil. Merkezi bir noktaya olan bu bağımlılık, IdP’nin bir yüksek erişilebilirlik (HA) konfigürasyonunda çalışmasını zorunlu kılar. Keycloak’ı production’da tekli instance olarak çalıştırmak, tek bir hata noktası oluşturur ve tüm sistemin çökmesine neden olabilir. Keycloak cluster kurulumu için en az 2 node ve önlerinde bir load balancer tercih edilmeli.
