AWS Cognito ile Kullanıcı Kimlik Doğrulama
Kullanıcı kimlik doğrulama, modern web ve mobil uygulamaların belki de en kritik parçası. Yanlış yapıldığında güvenlik açıkları, doğru yapıldığında ise bakımı zor, karmaşık bir altyapı ortaya çıkıyor. AWS Cognito tam da bu noktada devreye giriyor: kendi auth sunucunu yönetmek yerine, Amazon’un yönetilen kimlik hizmetinden yararlanıyorsun. Bu yazıda Cognito’yu sıfırdan kurup production’a hazır hale getireceğiz.
AWS Cognito Nedir ve Neden Kullanmalısın?
Cognito’yu kısaca tanımlamak gerekirse: kullanıcı kaydı, giriş, şifre sıfırlama, MFA, sosyal provider entegrasyonu ve JWT token yönetimini bir arada sunan yönetilen bir servistir. Kendi authentication sistemini yazmak yerine Cognito kullanmak, hem geliştirme süresini kısaltır hem de OWASP’ın sıraladığı auth açıklarının büyük bölümünü otomatik olarak kapatır.
Cognito iki ana bileşenden oluşur:
- User Pools: Kullanıcı dizini ve kimlik doğrulama mekanizması. E-posta/şifre, telefon, sosyal login burada yönetilir.
- Identity Pools: AWS kaynaklarına geçici erişim sağlar. S3 bucket’ına doğrudan erişim vermek istiyorsan burası devreye girer.
Çoğu senaryo için User Pool yeterli. Identity Pool genellikle mobil uygulamalarda veya kullanıcının doğrudan AWS servisleriyle konuşması gereken durumlarda lazım olur.
Ortam Kurulumu
Başlamadan önce AWS CLI kurulu ve yapılandırılmış olmalı. Ayrıca Boto3 kullanacağız, bu yüzden Python ortamını da hazırlayalım.
# AWS CLI kurulumu kontrolü
aws --version
# AWS CLI yapılandırması
aws configure
# AWS Access Key ID: [senin key'in]
# AWS Secret Access Key: [senin secret'ın]
# Default region name: eu-west-1
# Default output format: json
# Python bağımlılıklarını kur
pip install boto3 python-jose cryptography requests
Region seçimi önemli. Türkiye’deki kullanıcılar için eu-west-1 (İrlanda) veya eu-central-1 (Frankfurt) genellikle daha düşük gecikme süresi sağlar. KVKK uyumluluğu açısından verilerin AB içinde tutulması gerekiyorsa bu tercih daha da önemli hale geliyor.
User Pool Oluşturma
CLI ile User Pool oluşturalım. Bu yaklaşım Infrastructure as Code mantığına daha uygun ve tekrarlanabilir.
# User Pool oluştur
aws cognito-idp create-user-pool
--pool-name "MyAppUserPool"
--policies '{
"PasswordPolicy": {
"MinimumLength": 8,
"RequireUppercase": true,
"RequireLowercase": true,
"RequireNumbers": true,
"RequireSymbols": false,
"TemporaryPasswordValidityDays": 7
}
}'
--auto-verified-attributes email
--username-attributes email
--mfa-configuration "OPTIONAL"
--email-configuration '{
"EmailSendingAccount": "COGNITO_DEFAULT"
}'
--user-pool-tags '{"Environment":"production","Project":"myapp"}'
--region eu-west-1
Bu komut çalıştıktan sonra bir UserPoolId alacaksın. Bunu bir yere not et, her şeyde lazım olacak.
Şimdi bir App Client oluşturmamız gerekiyor. App Client, uygulamanın Cognito ile konuşmasını sağlayan anahtar seti gibi düşünebilirsin.
# App Client oluştur (secret olmadan, SPA ve mobil için)
aws cognito-idp create-user-pool-client
--user-pool-id eu-west-1_XXXXXXXXX
--client-name "MyAppWebClient"
--no-generate-secret
--explicit-auth-flows
ALLOW_USER_PASSWORD_AUTH
ALLOW_REFRESH_TOKEN_AUTH
ALLOW_USER_SRP_AUTH
--token-validity-units '{
"AccessToken": "hours",
"IdToken": "hours",
"RefreshToken": "days"
}'
--access-token-validity 1
--id-token-validity 1
--refresh-token-validity 30
--prevent-user-existence-errors ENABLED
--region eu-west-1
--prevent-user-existence-errors ENABLED ayarı önemli. Bu olmadan, bir e-posta adresiyle kayıt olunmaya çalışıldığında Cognito “bu kullanıcı zaten var” hatası döner ve bu bilgi sızdırma (user enumeration) açığına yol açar.
Server-side uygulamalar için --generate-secret ekle ve bu secret’ı güvenli bir şekilde sakla.
Python ile Temel Auth İşlemleri
Şimdi işin pratik kısmına geçelim. Gerçek dünyada kullanacağın kod bloklarına bakalım.
import boto3
import hmac
import hashlib
import base64
from botocore.exceptions import ClientError
# Konfigürasyon
REGION = "eu-west-1"
USER_POOL_ID = "eu-west-1_XXXXXXXXX"
CLIENT_ID = "your_client_id_here"
CLIENT_SECRET = None # Secret yoksa None bırak
client = boto3.client("cognito-idp", region_name=REGION)
def get_secret_hash(username):
"""Client secret varsa HMAC hesapla"""
if not CLIENT_SECRET:
return None
message = username + CLIENT_ID
dig = hmac.new(
CLIENT_SECRET.encode("utf-8"),
msg=message.encode("utf-8"),
digestmod=hashlib.sha256
).digest()
return base64.b64encode(dig).decode()
def register_user(email, password, name):
"""Yeni kullanıcı kaydı"""
try:
kwargs = {
"ClientId": CLIENT_ID,
"Username": email,
"Password": password,
"UserAttributes": [
{"Name": "email", "Value": email},
{"Name": "name", "Value": name},
]
}
secret_hash = get_secret_hash(email)
if secret_hash:
kwargs["SecretHash"] = secret_hash
response = client.sign_up(**kwargs)
print(f"Kullanıcı oluşturuldu. Doğrulama kodu e-postaya gönderildi.")
return response["UserSub"] # Kullanıcının UUID'si
except ClientError as e:
error_code = e.response["Error"]["Code"]
if error_code == "UsernameExistsException":
raise Exception("Bu e-posta adresi zaten kayıtlı.")
elif error_code == "InvalidPasswordException":
raise Exception("Şifre politikasına uymayan şifre.")
raise
def confirm_registration(email, confirmation_code):
"""E-posta doğrulama kodunu onayla"""
try:
kwargs = {
"ClientId": CLIENT_ID,
"Username": email,
"ConfirmationCode": confirmation_code
}
secret_hash = get_secret_hash(email)
if secret_hash:
kwargs["SecretHash"] = secret_hash
client.confirm_sign_up(**kwargs)
print("Hesap doğrulandı!")
except ClientError as e:
if e.response["Error"]["Code"] == "CodeMismatchException":
raise Exception("Geçersiz doğrulama kodu.")
raise
def login_user(email, password):
"""Kullanıcı girişi ve token alma"""
try:
kwargs = {
"AuthFlow": "USER_PASSWORD_AUTH",
"AuthParameters": {
"USERNAME": email,
"PASSWORD": password
},
"ClientId": CLIENT_ID
}
secret_hash = get_secret_hash(email)
if secret_hash:
kwargs["AuthParameters"]["SECRET_HASH"] = secret_hash
response = client.initiate_auth(**kwargs)
auth_result = response["AuthenticationResult"]
return {
"access_token": auth_result["AccessToken"],
"id_token": auth_result["IdToken"],
"refresh_token": auth_result["RefreshToken"],
"expires_in": auth_result["ExpiresIn"]
}
except ClientError as e:
if e.response["Error"]["Code"] == "NotAuthorizedException":
raise Exception("Hatalı e-posta veya şifre.")
elif e.response["Error"]["Code"] == "UserNotConfirmedException":
raise Exception("Hesap henüz doğrulanmamış.")
raise
JWT Token Doğrulama
Cognito’dan aldığın token’ları backend’inde doğrulamak için Cognito’nun public key’lerini kullanman gerekiyor. Bu kritik bir adım, atlama.
import requests
from jose import jwk, jwt
from jose.utils import base64url_decode
def get_cognito_public_keys():
"""Cognito'nun public key'lerini çek ve cache'le"""
jwks_url = f"https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}/.well-known/jwks.json"
response = requests.get(jwks_url)
return response.json()["keys"]
# Keys'i başlangıçta bir kez çek
PUBLIC_KEYS = get_cognito_public_keys()
def verify_token(token, token_use="access"):
"""
JWT token'ı doğrula
token_use: 'access' veya 'id'
"""
try:
# Header'dan kid'i al
headers = jwt.get_unverified_headers(token)
kid = headers["kid"]
# Eşleşen public key'i bul
public_key = None
for key in PUBLIC_KEYS:
if key["kid"] == kid:
public_key = key
break
if not public_key:
raise Exception("Public key bulunamadı.")
# Token'ı doğrula
claims = jwt.decode(
token,
public_key,
algorithms=["RS256"],
audience=CLIENT_ID if token_use == "id" else None
)
# Token kullanım tipini kontrol et
if claims.get("token_use") != token_use:
raise Exception(f"Yanlış token tipi. Beklenen: {token_use}")
return claims
except jwt.ExpiredSignatureError:
raise Exception("Token süresi dolmuş.")
except jwt.JWTError as e:
raise Exception(f"Geçersiz token: {str(e)}")
# Kullanım örneği
# claims = verify_token(access_token, token_use="access")
# user_id = claims["sub"]
# username = claims["username"]
Bu doğrulama mekanizması önemli çünkü her API isteğinde Cognito’ya sorma yerine, token’ı kendi backend’inde doğrulayabiliyorsun. Bu hem performans hem de maliyet açısından avantajlı.
MFA Kurulumu
Production ortamında MFA neredeyse zorunlu. Özellikle admin hesapları için mutlaka açık olmalı.
# Admin olarak kullanıcıya TOTP MFA zorunlu kıl
aws cognito-idp admin-set-user-mfa-preference
--user-pool-id eu-west-1_XXXXXXXXX
--username "[email protected]"
--software-token-mfa-settings '{
"Enabled": true,
"PreferredMfa": true
}'
--region eu-west-1
# Kullanıcı havuzunda MFA zorunluluğunu ayarla
aws cognito-idp set-user-pool-mfa-config
--user-pool-id eu-west-1_XXXXXXXXX
--software-token-mfa-configuration '{
"Enabled": true
}'
--mfa-configuration "ON"
--region eu-west-1
TOTP kurulumu sırasında kullanıcıya QR kod göstermen gerekiyor. Python tarafında bu şöyle işliyor:
def setup_totp_mfa(access_token, device_name="MyApp"):
"""TOTP MFA kurulumu için secret key al"""
try:
# TOTP secret key üret
response = client.associate_software_token(
AccessToken=access_token
)
secret_code = response["SecretCode"]
# Kullanıcıya gösterilecek QR URL'i oluştur
# Bu URL'i bir QR kod kütüphanesiyle görsele çevirebilirsin
username = "[email protected]" # Claims'den al
qr_url = (
f"otpauth://totp/{device_name}:{username}"
f"?secret={secret_code}&issuer={device_name}"
)
return {
"secret_code": secret_code,
"qr_url": qr_url
}
except ClientError as e:
raise Exception(f"TOTP kurulumu başarısız: {str(e)}")
def verify_totp_setup(access_token, totp_code, device_name="MyPhone"):
"""Kullanıcının girdiği TOTP kodunu doğrula ve MFA'yı aktifleştir"""
try:
client.verify_software_token(
AccessToken=access_token,
UserCode=totp_code,
FriendlyDeviceName=device_name
)
print("MFA başarıyla kuruldu!")
except ClientError as e:
if e.response["Error"]["Code"] == "EnableSoftwareTokenMFAException":
raise Exception("Geçersiz TOTP kodu.")
raise
Gerçek Dünya Senaryosu: E-Ticaret Uygulaması
Bir e-ticaret platformu için Cognito kurulumunda karşılaşılan tipik gereksinimleri ele alalım. Müşteri ve admin olmak üzere iki farklı kullanıcı grubu var ve bu grupların farklı yetkileri bulunuyor.
# Müşteri grubu oluştur
aws cognito-idp create-group
--group-name "Customers"
--user-pool-id eu-west-1_XXXXXXXXX
--description "Normal müşteriler"
--precedence 10
--region eu-west-1
# Admin grubu oluştur
aws cognito-idp create-group
--group-name "Admins"
--user-pool-id eu-west-1_XXXXXXXXX
--description "Yöneticiler"
--precedence 1
--region eu-west-1
# Kullanıcıyı gruba ekle
aws cognito-idp admin-add-user-to-group
--user-pool-id eu-west-1_XXXXXXXXX
--username "[email protected]"
--group-name "Admins"
--region eu-west-1
Backend tarafında grup kontrolü şöyle yapılır:
def check_user_group(access_token, required_group):
"""Kullanıcının belirtilen grupta olup olmadığını kontrol et"""
try:
response = client.get_user(AccessToken=access_token)
username = response["Username"]
groups_response = client.admin_list_groups_for_user(
UserPoolId=USER_POOL_ID,
Username=username
)
user_groups = [g["GroupName"] for g in groups_response["Groups"]]
if required_group not in user_groups:
raise Exception(f"Yetersiz yetki. Gerekli grup: {required_group}")
return True
except ClientError as e:
raise Exception(f"Yetki kontrolü başarısız: {str(e)}")
def admin_list_users(access_token, filter_query=None):
"""Admin paneli için kullanıcı listesi"""
# Önce admin yetkisi kontrol et
check_user_group(access_token, "Admins")
kwargs = {
"UserPoolId": USER_POOL_ID,
"Limit": 60
}
if filter_query:
kwargs["Filter"] = filter_query # örn: 'email ^= "test"'
response = client.list_users(**kwargs)
users = []
for user in response["Users"]:
user_data = {
"username": user["Username"],
"status": user["UserStatus"],
"created": user["UserCreateDate"].isoformat(),
"attributes": {
attr["Name"]: attr["Value"]
for attr in user["Attributes"]
}
}
users.append(user_data)
return users
Önemli Güvenlik Ayarları
Production’a almadan önce mutlaka yapılması gereken güvenlik yapılandırmaları var.
# Gelişmiş güvenlik modu aktifleştir (ek ücret var, ama değer)
aws cognito-idp update-user-pool
--user-pool-id eu-west-1_XXXXXXXXX
--user-pool-add-ons '{
"AdvancedSecurityMode": "ENFORCED"
}'
--account-recovery-setting '{
"RecoveryMechanisms": [
{"Priority": 1, "Name": "verified_email"}
]
}'
--region eu-west-1
# Lambda trigger ile özel doğrulama ekle
aws cognito-idp update-user-pool
--user-pool-id eu-west-1_XXXXXXXXX
--lambda-config '{
"PreAuthentication": "arn:aws:lambda:eu-west-1:123456789:function:pre-auth-check",
"PostAuthentication": "arn:aws:lambda:eu-west-1:123456789:function:post-auth-log",
"PreSignUp": "arn:aws:lambda:eu-west-1:123456789:function:pre-signup-validate"
}'
--region eu-west-1
Advanced Security Mode açıldığında Cognito şunları otomatik yapıyor:
- Adaptive Authentication: Şüpheli girişlerde ek doğrulama ister
- Compromised Credentials: Sızdırılmış şifre veritabanlarına karşı kontrol
- Anomaly Detection: Alışılmadık konum veya cihaz tespiti
Kullanıcı Yönetimi ve Bakım
Günlük operasyonlarda sıkça ihtiyaç duyduğun komutlar:
# Kullanıcıyı devre dışı bırak (hesabı silmeden)
aws cognito-idp admin-disable-user
--user-pool-id eu-west-1_XXXXXXXXX
--username "[email protected]"
--region eu-west-1
# Kullanıcının tüm oturumlarını sonlandır
aws cognito-idp admin-user-global-sign-out
--user-pool-id eu-west-1_XXXXXXXXX
--username "[email protected]"
--region eu-west-1
# Admin olarak şifre sıfırla
aws cognito-idp admin-set-user-password
--user-pool-id eu-west-1_XXXXXXXXX
--username "[email protected]"
--password "TempPass123!"
--permanent false
--region eu-west-1
# Kullanıcı havuzu istatistiklerini al
aws cognito-idp describe-user-pool
--user-pool-id eu-west-1_XXXXXXXXX
--region eu-west-1
--query 'UserPool.EstimatedNumberOfUsers'
--permanent false ayarı, kullanıcının bir sonraki girişte şifresi değiştirmek zorunda kalmasını sağlar. Güvenlik ihlali sonrası veya toplu şifre sıfırlama senaryolarında oldukça kullanışlı.
Yaygın Hatalar ve Çözümleri
Cognito ile çalışırken sık karşılaşılan sorunlar ve çözümleri:
- Token süresi doldu hatası: Access token 1 saat geçerli. Refresh token ile yenile. Refresh token’ı güvenli bir yerde sakla (httpOnly cookie önerilir).
- Rate limiting: Cognito varsayılan olarak saniyede 20 istek sınırı koyuyor. Yoğun dönemlerde bu limite çarpabilirsin, AWS Support’tan artış talep et.
- CORS hataları: User Pool’da Hosted UI kullanıyorsan, callback URL’leri tam olarak eşleşmeli, trailing slash bile fark yaratır.
- SRP auth akışı: Mobil uygulamalar için USER_PASSWORD_AUTH yerine USER_SRP_AUTH kullan, daha güvenli.
- Custom domain: Hosted UI için custom domain kurarken ACM sertifikasının
us-east-1bölgesinde olması gerekiyor, başka bölge çalışmaz. - Lambda trigger timeout: Pre-authentication Lambda’ların 5 saniye içinde cevap vermesi gerekiyor, timeout durumunda giriş başarısız oluyor.
Monitoring ve Alerting
# CloudWatch'ta başarısız giriş denemelerini izle
aws cloudwatch put-metric-alarm
--alarm-name "CognitoFailedLogins"
--alarm-description "Yüksek sayıda başarısız giriş denemesi"
--metric-name "SignInSuccesses"
--namespace "AWS/Cognito"
--dimensions Name=UserPool,Value=eu-west-1_XXXXXXXXX
--statistic Sum
--period 300
--threshold 100
--comparison-operator LessThanThreshold
--evaluation-periods 1
--alarm-actions "arn:aws:sns:eu-west-1:123456789:security-alerts"
--region eu-west-1
Cognito için şu CloudWatch metriklerini mutlaka izle:
- SignInSuccesses: Başarılı girişler
- SignInThrottles: Kısıtlanan istekler (rate limit)
- TokenRefreshSuccesses: Token yenileme başarısı
- UserNotFound: Kullanıcı bulunamadı hataları (user enumeration tespiti için)
Maliyet Optimizasyonu
Cognito’nun fiyatlandırması aylık aktif kullanıcı (MAU) başına. İlk 50.000 MAU ücretsiz. Bunun üzerinde ücretlendirme başlıyor. Bununla birlikte dikkat edilmesi gereken noktalar var:
- Advanced Security Mode ayrı ücretlendirilir, gerçekten ihtiyacın varsa kullan
- SAML ve OIDC federation her authentication işlemi için ek ücret çıkarır
- Sosyal login (Google, Facebook) standart MAU fiyatına dahil, federation fiyatına değil
- Kullanıcı havuzunu temiz tut, aylarca giriş yapmayan kullanıcıları periyodik olarak temizle
Sonuç
AWS Cognito, doğru yapılandırıldığında authentication altyapısını büyük ölçüde basitleştiriyor. Kendi auth sisteminizi yazmak yerine savaşta test edilmiş bir servise güvenmek, ekibin güvenlik uzmanı olmasını gerektirmeksizin sağlam bir temel atmanı sağlıyor.
Bu yazıda anlattıklarımı özetlemek gerekirse: User Pool oluşturmak, token doğrulama yapmak ve güvenlik ayarlarını düzgün yapmak asıl odak noktaların olmalı. MFA’yı opsiyonel değil, en azından admin hesapları için zorunlu tut. JWT token’larını her zaman kendi backend’inde doğrula, sadece Cognito’ya güvenme. Advanced Security Mode’u bütçen varsa mutlaka aç.
Bir sonraki adım olarak Cognito’yu API Gateway ile entegre edip, Lambda authorizer kullanmadan direkt Cognito User Pool authorizer kurmayı inceleyebilirsin. Bu, her API endpoint’ini ayrıca koruma altına almanın en temiz yolu. Uygulamanın büyüdükçe federasyon senaryolarına da bakman gerekecek, özellikle kurumsal müşterilere SAML desteği sunmak istiyorsan Cognito burada da oldukça yetenekli.
