Skip to content
~/sph.sh

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:

typescript
import * as cognito from 'aws-cdk-lib/aws-cognito';import * as iam from 'aws-cdk-lib/aws-iam';
// Authentication için User Poolconst 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 clientconst 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 Poolconst identityPool = new cognito.CfnIdentityPool(this, 'IdentityPool', {  allowUnauthenticatedIdentities: false,  cognitoIdentityProviders: [{    clientId: appClient.userPoolClientId,    providerName: userPool.userPoolProviderName,  }],});
// Scoped permission'larla authenticated roleconst 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 verauthenticatedRole.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: false yetkisiz user creation'ı önlüyor
  • advancedSecurityMode: ENFORCED compromised credential detection'ı aktifleştiriyor
  • mfa: OPTIONAL esneklik sağlıyor (asla REQUIRED kullanma - geri alınamaz)
  • generateSecret: true secret'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.

typescript
// Define Auth Challenge - Challenge sequence'ını orkestra ederexport 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ştururexport 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 Responseexport 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 session array'ine göre deterministic olmalı
  • Custom challenge'ları ayırt etmek için challengeMetadata kullan
  • privateChallengeParameters asla 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

typescript
// Pre Token Generation V2 - Hem ID hem de Access token'ları özelleştirexport 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:

typescript
// Tenant izolasyonuyla Shared User Poolconst 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 ataexport 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

typescript
// SAML provider için CDK setupconst 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

typescript
// CDK: Cognito authorizer ile API Gatewayconst 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 endpointconst 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 fonksiyonuexport 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 TTLPerformanceGüvenlikKullanım Alanı
YokEn yüksek latencyGerçek zamanlı permission'larYüksek güvenlikli operasyonlar
5 dakikaİyi denge~5 dakika gecikmeStandard API endpoint'leri
30-60 dakikaEn iyi performanceBayat permission'larRead-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

typescript
// 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:

  1. Hafta 1-2: User Migration Lambda implement et, staging kullanıcılarıyla test et
  2. Hafta 3-6: Lazy migration'ı aktifleştir, active user migration rate'ini izle
  3. Hafta 7-8: Kalan inactive kullanıcıları CSV veya API ile bulk import et
  4. 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

typescript
// 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 etexport 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ı:

  1. Compromised Credentials Protection: AWS, ihlal edilmiş credential database'lerini izliyor ve bilinen compromised password'lerle sign-in'leri blokluyor
  2. Adaptive Authentication: IP, device, location'a göre risk skorları ve risk seviyesi başına otomatik yanıtlar
  3. 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.

KriterAWS Amplifyamazon-cognito-identity-jsAWS SDK v3
Bundle Boyutu~500KB (tree-shakeable)~100KB~50KB (modular)
Kullanım AlanıFrontend app'ler (React, React Native)Custom UI ile frontendBackend/server-side
Secret DesteğiHayırHayırEvet
SRP AuthEvet, Built-inEvet, Built-inHayır, Manuel implementasyon
Token YönetimiEvet, OtomatikEvet, ManuelHayır, Manuel
OAuth Flow'larıEvet, Full destekSınırlıEvet, Full destek
SSR DesteğiSınırlı (Next.js/Nuxt)HayırEvet
BakımEvet, AktifSınırlı, DeprecatingEvet, Aktif

Amplify Frontend Implementasyonu

typescript
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

typescript
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

typescript
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:

  • SignInSuccesses ve SignInThrottles - Authentication health'ini izle
  • TokenRefreshSuccesses - 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:

  • ListUsers API 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.0025/MAU(10K50K),0.0025/MAU (10K-50K), 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

  1. Mimariyi Anla: User Pool'lar authenticate ediyor, Identity Pool'lar AWS erişimini authorize ediyor - birlikte çalışıyorlar ama farklı amaçlara hizmet ediyorlar

  2. Basit Başla, Karmaşıklığı Ölçeklendir: Temel authentication ile başla, business gereksinimleri ortaya çıktıkça Lambda trigger'ları ve federation ekle

  3. 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

  4. 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

  5. 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

  6. Kullanıcılarını Backup Al: Cognito backup sağlamıyor. İlk günden S3'e günlük user export implement et

  7. Akıllıca Cache'le: Authorizer caching performance'ı iyileştiriyor ama permission değişikliklerini geciktiriyor. Güvenlik gereksinimlerine göre denge kur

  8. Migration Zaman Alır: User migration kademeli. Inactive kullanıcılar için 30-60 günlük lazy migration artı bulk import planla

  9. Güvenlik Paraya Mal Oluyor: Advanced security Plus tier gerektiriyor ($0.02/MAU). Uygulamanız için risk vs maliyet değerlendirmesi yap

  10. 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