2025-12-24
Amazon Cognito Derinlemesine: Temel Authentication'ın Ötesinde
Amazon Cognito'nun gelişmiş özellikleri üzerine kapsamlı teknik kılavuz: özel authentication akışları, federation pattern'leri, multi-tenancy mimarileri, migration stratejileri ve production-grade güvenlik implementasyonu.
Özet
Amazon Cognito, uygulamalar için yönetilen authentication ve authorization sağlıyor, ancak production sistemleri temel sign-up ve sign-in akışlarından fazlasını gerektiriyor. Bu kılavuz, mid-to-senior developer’ların ölçeklenebilir, güvenli authentication sistemleri oluşturmak için ihtiyaç duydukları gelişmiş Cognito pattern’lerini inceliyor: multi-factor workflow’lar için özel Lambda trigger’ları, multi-tenant token customization için Pre Token Generation, enterprise identity provider’ları ile SAML/OIDC federation, caching stratejileriyle API Gateway entegrasyonu ve Auth0 veya özel sistemlerden sıfır downtime migration.
Cognito ile farklı projelerde çalışmak, asıl zorlukların tenant izolasyonu, federation karmaşıklığı ve MFA lock-in ve cross-region replication eksikliği gibi kısıtlamaları aşmada ortaya çıktığını öğretti. Bu kılavuz, çalışan CDK kodu, gerçekçi performance metrikleri ve ölçekte neyin işe yaradığına dair öğrenilmiş derslerle battle-test edilmiş pattern’ler sunuyor.
Mimariyi Anlamak
User Pools vs Identity Pools
User Pools ve Identity Pools arasındaki fark başlangıçta birçok developer’ı karıştırıyor. Temelde farklı amaçlara hizmet ediyorlar:
User Pools authentication’ı yönetiyor - kullanıcıların kim olduğunu doğruluyor. User directory’leri, credential’ları, MFA’yı, password policy’lerini ve OAuth flow’larını yönetiyorlar. Kullanıcılar sign-in olduğunda JWT token’ları (ID token, access token, refresh token) alıyorlar.
Identity Pools authorization’ı yönetiyor - client uygulamalarından S3, DynamoDB veya SQS gibi servislere doğrudan erişim için geçici AWS credential’ları sağlıyor. Authentication token’larını (User Pools veya external provider’lardan) AWS credential’larına exchange ediyorlar.
Her pattern ne zaman kullanılır:
- Sadece User Pool: API Gateway veya backend servislerini çağıran frontend
- Sadece Identity Pool: AWS kaynaklarına guest erişim (analytics, public data)
- İkisi birlikte: Frontend’den S3, DynamoDB’ye doğrudan erişen authenticated kullanıcılar
CDK ile Production Setup
User Pool ve Identity Pool’u uygun güvenlik konfigürasyonuyla gösteren complete bir setup:
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as iam from 'aws-cdk-lib/aws-iam';
// Authentication için User Pool
const userPool = new cognito.UserPool(this, 'UserPool', {
selfSignUpEnabled: false, // Production: User creation'ı kontrol et
signInAliases: { email: true, username: true },
autoVerify: { email: true },
passwordPolicy: {
minLength: 12,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: true,
},
accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
advancedSecurityMode: cognito.AdvancedSecurityMode.ENFORCED,
mfa: cognito.Mfa.OPTIONAL,
mfaSecondFactor: {
sms: true,
otp: true, // Time-based one-time password (TOTP)
},
});
// Web application için app client
const appClient = userPool.addClient('WebAppClient', {
authFlows: {
userPassword: false, // Daha az güvenli flow'u devre dışı bırak
userSrp: true, // Secure Remote Password
custom: true, // Custom auth flow'ları aktifleştir
},
oAuth: {
flows: {
authorizationCodeGrant: true,
implicitCodeGrant: false, // Production'da implicit flow'dan kaçın
},
scopes: [
cognito.OAuthScope.OPENID,
cognito.OAuthScope.EMAIL,
cognito.OAuthScope.PROFILE,
cognito.OAuthScope.custom('billing-api/read'),
],
callbackUrls: ['https://app.example.com/callback'],
logoutUrls: ['https://app.example.com/logout'],
},
generateSecret: true, // Server-side app'ler için gerekli
});
// AWS kaynaklarına erişim için Identity Pool
const identityPool = new cognito.CfnIdentityPool(this, 'IdentityPool', {
allowUnauthenticatedIdentities: false,
cognitoIdentityProviders: [{
clientId: appClient.userPoolClientId,
providerName: userPool.userPoolProviderName,
}],
});
// Scoped permission'larla authenticated role
const authenticatedRole = new iam.Role(this, 'CognitoAuthenticatedRole', {
assumedBy: new iam.FederatedPrincipal(
'cognito-identity.amazonaws.com',
{
StringEquals: {
'cognito-identity.amazonaws.com:aud': identityPool.ref,
},
'ForAnyValue:StringLike': {
'cognito-identity.amazonaws.com:amr': 'authenticated',
},
},
'sts:AssumeRoleWithWebIdentity'
),
});
// User-scoped path'lerle spesifik S3 erişimi ver
authenticatedRole.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:GetObject', 's3:PutObject'],
resources: ['arn:aws:s3:::my-bucket/${cognito-identity.amazonaws.com:sub}/*'],
}));
Önemli konfigürasyon kararları:
selfSignUpEnabled: falseyetkisiz user creation’ı önlüyoradvancedSecurityMode: ENFORCEDcompromised credential detection’ı aktifleştiriyormfa: OPTIONALesneklik sağlıyor (asla REQUIRED kullanma - geri alınamaz)generateSecret: truesecret’ları güvenli şekilde saklayabilen backend client’lar için
Custom Authentication Flow’ları
Custom authentication flow’ları, CAPTCHA doğrulama, güvenlik soruları veya passwordless authentication gibi karmaşık gereksinimleri mümkün kılıyor. Üç Lambda trigger, challenge sequence’ını orkestra etmek için birlikte çalışıyor.
Custom Auth Nasıl Çalışır
Multi-Factor Challenge Implementasyonu
Bu örnek complete bir flow implement ediyor: password → CAPTCHA → security question.
// Define Auth Challenge - Challenge sequence'ını orkestra eder
export const defineAuthChallenge = async (event: DefineAuthChallengeTrigger) => {
const session = event.request.session;
// İlk challenge: SRP password verification (Cognito tarafından handle edilir)
if (session.length === 0) {
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'SRP_A';
}
// İkinci challenge: SRP password verifier
else if (session.length === 1 && session[0].challengeName === 'SRP_A') {
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'PASSWORD_VERIFIER';
}
// Üçüncü challenge: CAPTCHA
else if (session.length === 2 && session[1].challengeName === 'PASSWORD_VERIFIER'
&& session[1].challengeResult === true) {
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
event.response.challengeMetadata = 'CAPTCHA_CHALLENGE';
}
// Dördüncü challenge: Security question
else if (session.length === 3 && session[2].challengeName === 'CUSTOM_CHALLENGE'
&& session[2].challengeResult === true) {
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
event.response.challengeMetadata = 'SECURITY_QUESTION';
}
// Tüm challenge'lar başarılı
else if (session.length === 4 && session[3].challengeName === 'CUSTOM_CHALLENGE'
&& session[3].challengeResult === true) {
event.response.issueTokens = true;
event.response.failAuthentication = false;
}
// Challenge başarısız
else {
event.response.issueTokens = false;
event.response.failAuthentication = true;
}
return event;
};
// Create Auth Challenge - Challenge data'sını oluşturur
export const createAuthChallenge = async (event: CreateAuthChallengeTrigger) => {
const metadata = event.request.challengeMetadata;
if (metadata === 'CAPTCHA_CHALLENGE') {
// External servis veya internal logic kullanarak CAPTCHA oluştur
const captchaToken = await generateCaptcha();
event.response.publicChallengeParameters = {
captchaUrl: `https://captcha.example.com/${captchaToken}`,
challengeType: 'CAPTCHA',
};
event.response.privateChallengeParameters = {
captchaAnswer: await getCaptchaAnswer(captchaToken),
};
}
else if (metadata === 'SECURITY_QUESTION') {
// DynamoDB'den kullanıcının güvenlik sorusunu al
const question = await getSecurityQuestion(event.userName);
event.response.publicChallengeParameters = {
question: question.text,
challengeType: 'SECURITY_QUESTION',
};
event.response.privateChallengeParameters = {
answer: question.answer,
};
}
return event;
};
// Verify Auth Challenge Response
export const verifyAuthChallenge = async (event: VerifyAuthChallengeTrigger) => {
const privateParams = event.request.privateChallengeParameters;
const challengeAnswer = event.request.challengeAnswer;
if (privateParams.captchaAnswer) {
event.response.answerCorrect =
challengeAnswer.toLowerCase() === privateParams.captchaAnswer.toLowerCase();
}
else if (privateParams.answer) {
event.response.answerCorrect =
challengeAnswer.toLowerCase() === privateParams.answer.toLowerCase();
}
return event;
};
Kritik implementasyon detayları:
- Challenge sequence
sessionarray’ine göre deterministic olmalı - Custom challenge’ları ayırt etmek için
challengeMetadatakullan privateChallengeParametersasla client’a gönderilmez, sadece verification için kullanılır- Her trigger’ın 5 saniyelik timeout limiti var - logic’i hızlı tut
Multi-Tenancy için Token Customization
Pre Token Generation Lambda, JWT token’larına custom claim’ler eklemeye izin veriyor - tenant context’in her request ile birlikte travel etmesi gereken multi-tenant SaaS uygulamaları için essential.
Pre Token Generation V2
// Pre Token Generation V2 - Hem ID hem de Access token'ları özelleştir
export const preTokenGeneration = async (event: PreTokenGenerationTriggerEvent) => {
// DynamoDB'den tenant ve role bilgisini al
const userMetadata = await getUserMetadata(event.userName);
if (event.request.userAttributes['custom:tenantId']) {
const tenantId = event.request.userAttributes['custom:tenantId'];
// Tenant'ın active olduğunu doğrula
const tenant = await getTenantById(tenantId);
if (!tenant || tenant.status !== 'ACTIVE') {
throw new Error('Tenant is not active');
}
// ID token'a custom claim'ler ekle (user info için)
event.response.claimsOverrideDetails = {
claimsToAddOrOverride: {
'custom:tenantId': tenantId,
'custom:tenantName': tenant.name,
'custom:organizationId': tenant.organizationId,
'custom:role': userMetadata.role,
'custom:permissions': JSON.stringify(userMetadata.permissions),
},
};
// Access Token'ı özelleştir (sadece Cognito Essentials/Plus tier)
if (event.triggerSource === 'TokenGeneration_Authentication') {
event.response.claimsOverrideDetails.accessTokenGeneration = {
claimsToAddOrOverride: {
'tenant_id': tenantId,
'role': userMetadata.role,
},
claimsToSuppress: [],
scopesToAdd: [`tenant:${tenantId}:read`, `tenant:${tenantId}:write`],
};
}
}
// Feature flag'ler için subscription tier ekle
if (userMetadata.subscriptionTier) {
event.response.claimsOverrideDetails.claimsToAddOrOverride['custom:tier'] =
userMetadata.subscriptionTier;
}
return event;
};
// DynamoDB helper fonksiyonları
async function getUserMetadata(username: string) {
const result = await dynamoDB.get({
TableName: 'UserMetadata',
Key: { username },
}).promise();
return result.Item || { role: 'user', permissions: [] };
}
async function getTenantById(tenantId: string) {
const result = await dynamoDB.get({
TableName: 'Tenants',
Key: { tenantId },
}).promise();
return result.Item;
}
Güvenlik dikkat noktaları:
- Token’lara asla sensitive data (password’ler, API key’ler) ekleme
- Token boyutunu HTTP header limitleri için 8KB’ın altında tut
- Büyük permission set’leri için opaque reference’lar kullan
- Token forgery’yi önlemek için tenant context’i doğrula
Warning: Token Size Pitfall: Çok fazla custom claim eklemek, token’ları 8KB üzerine çıkarıp HTTP 431 hatalarına neden olabilir. Production’da token boyutunu izle ve büyük veri yapıları yerleştirmek yerine reference ID’leri kullan.
Multi-Tenancy Pattern’leri
Doğru multi-tenancy pattern’ini seçmek, ölçeklenebilirliği, izolasyonu ve operasyonel karmaşıklığı önemli ölçüde etkiliyor.
Custom Attribute’larla Shared Pool
Bu pattern, 100’den az tenant’lı çoğu SaaS uygulaması için iyi çalışıyor:
// Tenant izolasyonuyla Shared User Pool
const userPool = new cognito.UserPool(this, 'MultiTenantUserPool', {
selfSignUpEnabled: false,
standardAttributes: {
email: { required: true, mutable: true },
},
customAttributes: {
tenantId: new cognito.StringAttribute({
minLen: 1,
maxLen: 128,
mutable: false, // Oluşturulduktan sonra tenant değiştirilemez
}),
organizationId: new cognito.StringAttribute({
minLen: 1,
maxLen: 128,
mutable: false,
}),
role: new cognito.StringAttribute({
minLen: 1,
maxLen: 64,
mutable: true, // Role güncellenebilir
}),
},
});
// Pre Sign Up - Invitation token'dan tenant ata
export const preSignUp = async (event: PreSignUpTriggerEvent) => {
const invitationToken = event.request.validationData?.invitationToken;
if (!invitationToken) {
throw new Error('Invitation token required');
}
// Invitation'ı doğrula ve tenant bilgisini al
const invitation = await validateInvitation(invitationToken);
if (!invitation || invitation.expired) {
throw new Error('Invalid or expired invitation');
}
// Auto-confirm ve tenant attribute'larını set et
event.response.autoConfirmUser = true;
event.response.autoVerifyEmail = true;
// Bunlar custom attribute olarak set edilecek
event.request.userAttributes['custom:tenantId'] = invitation.tenantId;
event.request.userAttributes['custom:organizationId'] = invitation.organizationId;
event.request.userAttributes['custom:role'] = invitation.role;
// Invitation'ı kullanıldı olarak işaretle
await markInvitationUsed(invitationToken, event.userName);
return event;
};
Pattern seçim gerçeği: Çoğu uygulama shared pool + custom attribute’larla başlıyor, sadece tenant sayısı 100’ü geçtiğinde veya güvenlik gereksinimleri daha güçlü izolasyon talep ettiğinde groups-based izolasyona migrate ediyorlar.
Enterprise Identity Provider’ları ile SAML Federation
Federation, kullanıcıların Azure AD, Okta veya OneLogin gibi kurumsal identity provider’ları üzerinden authenticate olmasını sağlıyor - B2B SaaS uygulamaları için essential.
Azure AD SAML Konfigürasyonu
// SAML provider için CDK setup
const samlProvider = new cognito.UserPoolIdentityProviderSaml(this, 'AzureADProvider', {
userPool,
name: 'AzureAD',
metadata: cognito.UserPoolIdentityProviderSamlMetadata.url(
'https://login.microsoftonline.com/TENANT_ID/federationmetadata/2007-06/federationmetadata.xml'
),
attributeMapping: {
email: cognito.ProviderAttribute.other('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'),
givenName: cognito.ProviderAttribute.other('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'),
familyName: cognito.ProviderAttribute.other('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname'),
custom: {
'tenantId': cognito.ProviderAttribute.other('http://schemas.microsoft.com/identity/claims/tenantid'),
},
},
idpSignout: true,
});
// Federated user'ı mevcut profile link et (duplicate'leri önle)
export const postAuthentication = async (event: PostAuthenticationTriggerEvent) => {
// Bu federated bir identity mi kontrol et
if (event.request.userAttributes.identities) {
const identities = JSON.parse(event.request.userAttributes.identities);
const federatedIdentity = identities[0];
if (federatedIdentity.providerName === 'AzureAD') {
const email = event.request.userAttributes.email;
// Bu email ile zaten bir kullanıcı var mı kontrol et
const existingUser = await findUserByEmail(email);
if (existingUser && existingUser.username !== event.userName) {
// Federated identity'yi mevcut kullanıcıya link et
await cognito.adminLinkProviderForUser({
UserPoolId: event.userPoolId,
DestinationUser: {
ProviderName: 'Cognito',
ProviderAttributeValue: existingUser.username,
},
SourceUser: {
ProviderName: federatedIdentity.providerName,
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: federatedIdentity.userId,
},
}).promise();
// Audit için linking'i logla
await auditLog({
action: 'FEDERATED_IDENTITY_LINKED',
email,
provider: federatedIdentity.providerName,
});
}
}
}
return event;
};
Federation best practice’leri:
- Otomatik certificate rotation için metadata URL kullan
- NameId’yi immutable attribute’a (user_id) map et, email’e değil
- Duplicate kullanıcıları önlemek için account linking implement et
- Hem SP-initiated hem de IdP-initiated logout flow’larını test et
Tip: Federation Testing: Logout flow’larını detaylıca test et. Federated logout, Cognito, IdP ve uygulama arasında koordinasyon gerektiriyor. Kullanıcıların app’te logout görünmesi ama IdP seviyesinde hala authenticated olması yaygın bir sorun.
API Gateway Entegrasyonu
API Gateway Cognito authorizer’ları, JWT token’larını doğrular ve performance için authorization kararlarını cache’ler.
Complete Integration Setup
// CDK: Cognito authorizer ile API Gateway
const api = new apigateway.RestApi(this, 'MyApi', {
restApiName: 'Secure API',
deployOptions: {
stageName: 'prod',
tracingEnabled: true,
},
});
const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, 'CognitoAuthorizer', {
cognitoUserPools: [userPool],
authorizerName: 'CognitoAuthorizer',
identitySource: 'method.request.header.Authorization',
resultsCacheTtl: Duration.minutes(5), // Authorization kararlarını cache'le
});
// Spesifik OAuth scope gerektiren protected endpoint
const protectedResource = api.root.addResource('billing');
protectedResource.addMethod('GET', new apigateway.LambdaIntegration(billingFunction), {
authorizer,
authorizationType: apigateway.AuthorizationType.COGNITO,
authorizationScopes: ['billing-api/read'], // OAuth scope validation
requestValidator: new apigateway.RequestValidator(this, 'RequestValidator', {
restApi: api,
validateRequestBody: true,
validateRequestParameters: true,
}),
});
// JWT validation ve tenant izolasyonu ile Lambda fonksiyonu
export const handler = async (event: APIGatewayProxyEvent) => {
// API Gateway JWT'yi zaten doğruladı, claim'leri çıkar
const claims = event.requestContext.authorizer?.claims;
if (!claims) {
return { statusCode: 401, body: 'Unauthorized' };
}
const tenantId = claims['custom:tenantId'];
const role = claims['custom:role'];
// Tenant context'i doğrula
if (!tenantId) {
return { statusCode: 403, body: 'Missing tenant context' };
}
// Tenant izolasyonuyla query
const result = await dynamoDB.query({
TableName: 'BillingRecords',
IndexName: 'TenantIndex',
KeyConditionExpression: 'tenantId = :tenantId',
ExpressionAttributeValues: {
':tenantId': tenantId,
},
}).promise();
// Role-based filtering uygula
const filteredRecords = filterByRole(result.Items, role);
return {
statusCode: 200,
body: JSON.stringify(filteredRecords),
};
};
Authorization caching trade-off’ları:
| Cache TTL | Performance | Güvenlik | Kullanım Alanı |
|---|---|---|---|
| Yok | En yüksek latency | Gerçek zamanlı permission’lar | Yüksek güvenlikli operasyonlar |
| 5 dakika | İyi denge | ~5 dakika gecikme | Standard API endpoint’leri |
| 30-60 dakika | En iyi performance | Bayat permission’lar | Read-only public data |
Çeşitli caching stratejileriyle çalışmak, 5 dakikalık TTL’nin çoğu uygulama için performance ve güvenlik arasında iyi bir denge sağladığını gösterdi.
External Auth Provider’lardan Migration
User Migration Lambda, lazy migration kullanarak Auth0, Okta veya custom authentication sistemlerinden sıfır downtime migration’ı mümkün kılıyor.
Lazy Migration Stratejisi
// User Migration Lambda - Lazy migration yaklaşımı
export const userMigration = async (event: UserMigrationTriggerEvent) => {
if (event.triggerSource === 'UserMigration_Authentication') {
// Kullanıcı sign-in olmaya çalışıyor ama Cognito'da yok
const { userName, password } = event.request;
try {
// Credential'ları Auth0'a karşı doğrula
const auth0User = await validateWithAuth0(userName, password);
if (auth0User) {
// Kullanıcı geçerli, Cognito'ya migrate et
event.response.userAttributes = {
email: auth0User.email,
email_verified: 'true',
given_name: auth0User.given_name,
family_name: auth0User.family_name,
'custom:auth0Id': auth0User.user_id,
'custom:migratedAt': new Date().toISOString(),
};
event.response.finalUserStatus = 'CONFIRMED';
event.response.messageAction = 'SUPPRESS'; // Welcome email gönderme
// Tracking için migration'ı logla
await logMigration(userName, 'success');
return event;
}
} catch (error) {
await logMigration(userName, 'failed', error);
throw error;
}
}
if (event.triggerSource === 'UserMigration_ForgotPassword') {
// Kullanıcı password reset istiyor ama Cognito'da yok
const { userName } = event.request;
// Kullanıcı Auth0'da var mı kontrol et
const auth0User = await getUserFromAuth0(userName);
if (auth0User) {
event.response.userAttributes = {
email: auth0User.email,
email_verified: 'true',
'custom:auth0Id': auth0User.user_id,
};
event.response.messageAction = 'SUPPRESS';
return event;
}
}
throw new Error('User not found in legacy system');
};
async function validateWithAuth0(username: string, password: string) {
const response = await axios.post('https://YOUR_DOMAIN.auth0.com/oauth/token', {
grant_type: 'password',
username,
password,
client_id: process.env.AUTH0_CLIENT_ID,
client_secret: process.env.AUTH0_CLIENT_SECRET,
audience: process.env.AUTH0_AUDIENCE,
scope: 'openid profile email',
});
if (response.data.access_token) {
// User info al
const userInfo = await axios.get('https://YOUR_DOMAIN.auth0.com/userinfo', {
headers: { Authorization: `Bearer ${response.data.access_token}` },
});
return userInfo.data;
}
return null;
}
Migration timeline:
- Hafta 1-2: User Migration Lambda implement et, staging kullanıcılarıyla test et
- Hafta 3-6: Lazy migration’ı aktifleştir, active user migration rate’ini izle
- Hafta 7-8: Kalan inactive kullanıcıları CSV veya API ile bulk import et
- Hafta 9+: Tüm kullanıcıların migrate olduğunu doğruladıktan sonra legacy sistemi kapat
Bu yaklaşım, bir projede kullanıcıları 60 gün boyunca kademeli olarak migrate etti - %80’i lazy authentication ile, %20’si bulk import ile.
Gelişmiş Güvenlik Özellikleri
Cognito’nun advanced security özellikleri Plus tier pricing gerektiriyor ancak enterprise-grade koruma sağlıyor.
Security Konfigürasyonu
// Advanced Security'yi aktifleştir (Plus tier gerekli)
const userPool = new cognito.UserPool(this, 'SecureUserPool', {
advancedSecurityMode: cognito.AdvancedSecurityMode.ENFORCED,
userPoolAddOns: {
advancedSecurityMode: cognito.AdvancedSecurityMode.ENFORCED,
},
signInAliases: { email: true },
signInCaseSensitive: false,
});
// Post Authentication - Risk seviyelerini handle et
export const postAuthentication = async (event: PostAuthenticationTriggerEvent) => {
const riskLevel = event.request.userContextData?.encodedData
? parseRiskData(event.request.userContextData.encodedData)
: 'LOW';
// Risk seviyesiyle authentication'ı logla
await logAuthentication({
username: event.userName,
riskLevel,
ipAddress: event.request.userContextData?.ipAddress,
deviceKey: event.request.userContextData?.deviceKey,
timestamp: new Date().toISOString(),
});
// Yüksek riskli authentication'lar için ek güvenlik tetikle
if (riskLevel === 'HIGH' || riskLevel === 'MEDIUM') {
await sendSecurityAlert(event.userName, riskLevel);
if (riskLevel === 'HIGH') {
await setUserMFARequired(event.userPoolId, event.userName);
}
}
return event;
};
Üç güvenlik katmanı:
- Compromised Credentials Protection: AWS, ihlal edilmiş credential database’lerini izliyor ve bilinen compromised password’lerle sign-in’leri blokluyor
- Adaptive Authentication: IP, device, location’a göre risk skorları ve risk seviyesi başına otomatik yanıtlar
- MFA Seçenekleri: SMS (en yüksek sürtünme), TOTP (dengeli), WebAuthn/FIDO2 (en düşük sürtünme)
Warning: MFA Configuration Lock-in: MFA bir kez “REQUIRED” olarak ayarlandığında (herhangi bir metod için: SMS, TOTP veya WebAuthn), pool’u yeniden oluşturmadan devre dışı bırakamaz veya “OPTIONAL“‘a çeviremezsin. Her zaman “OPTIONAL” kullan ve MFA’yı uygulama logic’i veya adaptive authentication ile seçici olarak enforce et.
SDK Karşılaştırması: Amplify vs AWS SDK
Doğru client library’yi seçmek, bundle boyutunu, özellikleri ve maintenance yükünü etkiliyor.
| Kriter | AWS Amplify | amazon-cognito-identity-js | AWS SDK v3 |
|---|---|---|---|
| Bundle Boyutu | ~500KB (tree-shakeable) | ~100KB | ~50KB (modular) |
| Kullanım Alanı | Frontend app’ler (React, React Native) | Custom UI ile frontend | Backend/server-side |
| Secret Desteği | Hayır | Hayır | Evet |
| SRP Auth | Evet, Built-in | Evet, Built-in | Hayır, Manuel implementasyon |
| Token Yönetimi | Evet, Otomatik | Evet, Manuel | Hayır, Manuel |
| OAuth Flow’ları | Evet, Full destek | Sınırlı | Evet, Full destek |
| SSR Desteği | Sınırlı (Next.js/Nuxt) | Hayır | Evet |
| Bakım | Evet, Aktif | Sınırlı, Deprecating | Evet, Aktif |
Amplify Frontend Implementasyonu
import { Amplify } from 'aws-amplify';
import { signIn, signOut, getCurrentUser } from 'aws-amplify/auth';
Amplify.configure({
Auth: {
Cognito: {
userPoolId: 'us-east-1_ABC123',
userPoolClientId: 'abc123def456',
identityPoolId: 'us-east-1:abc123-def456',
loginWith: {
oauth: {
domain: 'auth.example.com',
scopes: ['openid', 'email', 'profile', 'billing-api/read'],
redirectSignIn: ['https://app.example.com/callback'],
redirectSignOut: ['https://app.example.com/logout'],
responseType: 'code',
},
},
},
},
});
async function handleSignIn(email: string, password: string) {
try {
const { isSignedIn, nextStep } = await signIn({
username: email,
password,
});
if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_TOTP_CODE') {
const code = await promptForMFACode();
await confirmSignIn({ challengeResponse: code });
}
// Token'lar otomatik olarak saklanıyor ve refresh ediliyor
const user = await getCurrentUser();
return user;
} catch (error) {
console.error('Sign in error:', error);
throw error;
}
}
AWS SDK Backend Implementasyonu
import {
CognitoIdentityProviderClient,
AdminInitiateAuthCommand,
} from '@aws-sdk/client-cognito-identity-provider';
import { createHmac } from 'crypto';
const client = new CognitoIdentityProviderClient({ region: 'us-east-1' });
function calculateSecretHash(username: string): string {
const message = username + process.env.COGNITO_CLIENT_ID;
const hash = createHmac('sha256', process.env.COGNITO_CLIENT_SECRET!)
.update(message)
.digest('base64');
return hash;
}
async function authenticateUser(username: string, password: string) {
const command = new AdminInitiateAuthCommand({
UserPoolId: process.env.USER_POOL_ID,
ClientId: process.env.COGNITO_CLIENT_ID,
AuthFlow: 'ADMIN_USER_PASSWORD_AUTH',
AuthParameters: {
USERNAME: username,
PASSWORD: password,
SECRET_HASH: calculateSecretHash(username),
},
});
const response = await client.send(command);
return {
accessToken: response.AuthenticationResult?.AccessToken,
idToken: response.AuthenticationResult?.IdToken,
refreshToken: response.AuthenticationResult?.RefreshToken,
expiresIn: response.AuthenticationResult?.ExpiresIn,
};
}
Seçim kılavuzu: Otomatik token yönetimi olan React/React Native frontend uygulamaları için Amplify kullan. Client secret’ları ve custom authentication flow’ları gerektiren backend servisleri için AWS SDK kullan.
Production Pattern’leri ve Monitoring
Token Refresh Stratejisi
const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000; // 5 dakika
async function getValidToken(): Promise<string> {
const session = await Auth.currentSession();
const expiresAt = session.getAccessToken().getExpiration() * 1000;
if (Date.now() + TOKEN_REFRESH_THRESHOLD > expiresAt) {
const newSession = await Auth.currentSession();
return newSession.getAccessToken().getJwtToken();
}
return session.getAccessToken().getJwtToken();
}
Essential CloudWatch Metrikleri
Track edilmesi gereken authentication metrikleri:
SignInSuccessesveSignInThrottles- Authentication health’ini izleTokenRefreshSuccesses- Token refresh failure’larını track et- Custom metrikler: Authentication süresi, MFA completion rate
- Alarm’lar: Yüksek failure rate, throttling, advanced security block’ları
Güvenlik metrikleri:
- Compromised credential tespitleri
- Yüksek riskli authentication girişimleri
- Adaptive authentication tetiklemeleri
- Account takeover prevention rate
Yaygın Pitfall’lar ve Çözümler
Pitfall 1: Backup Stratejisi Yok
Problem: Cognito User Pool’ları backup alınamıyor veya region’lar arası replicate edilemiyor. Yanlışlıkla silme veya region failure, total user data kaybı anlamına geliyor.
Çözüm:
ListUsersAPI kullanarak günlük user data’yı S3’e export et- Kritik user metadata’yı DynamoDB’de sakla
- Otomatik export’lar için scheduled Lambda implement et
- Pool recreation prosedürünü dokümante et
Bu, Cognito’nun en büyük kısıtlaması. İlk günden backup process’leri oluşturmak, felaketten kaçınmanın yolu.
Pitfall 2: Token Size Limitleri
Problem: Çok fazla custom claim eklemek, token’ların 8KB header limitlerini aşmasına neden oluyor ve HTTP 431 hatalarıyla sonuçlanıyor.
Çözüm:
- Büyük dataset’leri DynamoDB’de sakla, token’a reference ID ekle
- Full object’ler yerleştirmek yerine opaque ID’ler kullan
- Production’da token boyutunu izle
- Büyük permission set’leri için pagination implement et
Örnek: Tüm permission’ları embed etmek yerine permissionSetId: "ps-123" ekle ve detayları cache’ten çek.
Pitfall 3: Authorizer Cache Invalidation
Problem: API Gateway, authorization kararlarını cache’liyor. Revoke edilen permission’lar, cache expire olana kadar çalışmaya devam ediyor.
Çözüm:
- Hassas operasyonlar için daha kısa TTL (5-15 dakika) kullan
- Authorization header’a version ekleyerek cache busting implement et
- Gerçek zamanlı permission kontrolü için Lambda authorizer kullan
- Security ekibi için cache davranışını dokümante et
Çeşitli caching stratejileriyle çalışmak, 5 dakikalık TTL’nin çoğu uygulama için performance ve güvenlik arasında iyi denge sağladığını gösterdi.
Pitfall 4: SMS Region Kısıtlamaları
Problem: SMS gönderimi AWS End User Messaging SMS (eski adı SNS) üzerinden tüm Cognito region’larında desteklenmiyor, beklenmedik verification failure’larına neden oluyor.
Çözüm:
- Cognito region’ın için AWS End User Messaging SMS desteğini kontrol et
- SMS spending limitini doğru region’da konfigüre et
- Production region’da launch öncesi SMS delivery’yi test et
- Email verification’a fallback implement et
Pitfall 5: Lambda Trigger Timeout’ları
Problem: Lambda trigger’lar, sync trigger’lar için 5 saniyelik timeout’a sahip (Pre/Post Auth), yavaş external API’lerle authentication failure’larına neden oluyor.
Çözüm:
- Trigger logic’ini 3 saniyenin altında tut
- Kritik olmayan görevler için async operasyonlar kullan
- External API response’larını cache’le
- External dependency’ler için circuit breaker implement et
- Lambda duration ve error’larını izle
Pattern: Sync trigger’larda kritik validation yap, analytics ve logging’i async process’lere push et.
Maliyet Analizi
Fiyatlandırma Tier’ları (Aralık 2024)
Lite tier (10,000 MAU ücretsiz, sonra kademeli fiyatlandırma):
- Temel authentication, MFA, social provider’lar
- Advanced security yok
- Free tier sonrası kademeli fiyatlandırma: 0.00375/MAU (50K-100K), vb.
Essentials tier ($0.015/MAU):
- Advanced security (audit mode)
- Access token customization
Plus tier ($0.02/MAU):
- Advanced security (enforced mode)
- SAML/OIDC federation
- Essentials’a göre 1.33x maliyet
Gizli maliyetler:
- SMS MFA: US’de $0.00645/mesaj (AWS End User Messaging SMS ile, eski adı SNS)
- Lambda trigger invocation’ları: 1M request başına $0.20
- API Gateway authorizer call’ları (caching devre dışıysa)
Maliyet optimizasyonu:
- Inactive kullanıcıları otomatik olarak archive et
- Direct user sayısını azaltmak için federation kullan
- MAU büyüme trendlerini izle
- Düşük trafikli API’ler için Lambda authorizer düşün
Cognito vs Alternatifler Ne Zaman Seçilmeli
Cognito Şunlar İçin Mantıklı:
- AWS-native mimari
- Standard authentication gereksinimleri
- Budget-conscious projeler
- Hızlı MVP geliştirme
- Küçük-orta ölçek (< 10M kullanıcı)
Alternatifler İçin Düşün:
Auth0: Karmaşık authentication flow’ları, kapsamlı özelleştirme, enterprise SLA gereksinimleri, global compliance ihtiyaçları
Okta: Workforce identity (çalışanlar), enterprise SSO, gelişmiş lifecycle yönetimi
Custom Çözüm: Benzersiz authentication gereksinimleri, tam data kontrolü, mevcut identity altyapısı, çok yüksek ölçek (> 100M kullanıcı)
Kabul Edilmesi Gereken Cognito Kısıtlamaları:
- Cross-region replication yok
- Sınırlı user management API’leri
- 3KB CSS özelleştirme limiti
- Doğrudan database erişimi yok
- MFA konfigürasyon lock-in
Önemli Çıkarımlar
-
Mimariyi Anla: User Pool’lar authenticate ediyor, Identity Pool’lar AWS erişimini authorize ediyor - birlikte çalışıyorlar ama farklı amaçlara hizmet ediyorlar
-
Basit Başla, Karmaşıklığı Ölçeklendir: Temel authentication ile başla, business gereksinimleri ortaya çıktıkça Lambda trigger’ları ve federation ekle
-
Multi-Tenancy’yi Erken Planla: Launch sonrası tenant izolasyon pattern’lerini değiştirmek acı verici. Shared pool + custom attribute’lar çoğu SaaS uygulaması için iyi çalışıyor
-
Custom Claim’ler Fine-Grained Authorization’ı Sağlıyor: Pre Token Generation V2, ekstra API call’ları olmadan token’lara tenant context ve permission’lar ekliyor
-
Federation Karmaşık: SAML/OIDC entegrasyonu beklenenden uzun sürüyor. Logout flow’larını ve attribute mapping’i test etmek için zaman ayır
-
Kullanıcılarını Backup Al: Cognito backup sağlamıyor. İlk günden S3’e günlük user export implement et
-
Akıllıca Cache’le: Authorizer caching performance’ı iyileştiriyor ama permission değişikliklerini geciktiriyor. Güvenlik gereksinimlerine göre denge kur
-
Migration Zaman Alır: User migration kademeli. Inactive kullanıcılar için 30-60 günlük lazy migration artı bulk import planla
-
Güvenlik Paraya Mal Oluyor: Advanced security Plus tier gerektiriyor ($0.02/MAU). Uygulamanız için risk vs maliyet değerlendirmesi yap
-
Limitleri Bil: Cognito’nun keskin köşeleri var (MFA lock-in, replication yok, sınırlı UI özelleştirme). Kısıtlamalar dahilinde çalış veya alternatif seç
Farklı projelerde Cognito ile çalışmak, başarının bu kısıtlamaları erken anlamaktan ve içlerinde çalışan pattern’ler oluşturmaktan geldiğini öğretti. Servis, mimari sınırlarını kabul edip buna göre plan yaptığında authentication’ı iyi handle ediyor.
İlgili yazılar
AWS CDK, Lambda ve GitHub Actions kullanarak otomatik preview ortamları oluşturmayı öğrenin - sorunsuz PR test ve inceleme süreçleri için
Global uygulamalar için AWS edge computing çözümlerini seçme ve uygulama üzerine pratik örnekler ve maliyet optimizasyonu stratejileri içeren kapsamlı teknik rehber.
AWS Secrets Manager ve Systems Manager Parameter Store'u karşılaştıran kapsamlı teknik rehber - hangi servisi ne zaman kullanmalı ve gerçek dünya implementation pattern'leri.
Amazon SNS ve SQS kullanarak güvenli cross-account event dağıtımı nasıl yapılır öğrenin. IAM policy'leri, KMS şifreleme, AWS CDK implementasyonu ve production'da karşılaşılan yaygın sorunları kapsıyor.
Dev, staging ve production ortamlarında Lambda Layer versiyonlarını yönetmek için pratik yaklaşımlar. AWS CDK implementasyonları, otomatik deployment pipeline'ları ve rollback stratejileri ile.