İçeriğe atla

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.

AWS Services

Application Layer

Authentication Layer

JWT Tokens

JWT Token

Temporary AWS Credentials

JWT Token

AWS Credentials

AWS Credentials

AWS Credentials

Kullanici

Cognito User Pool

Authentication

Cognito Identity Pool

Authorization

Web Application

API Gateway

Cognito Authorizer

Lambda Functions

S3 Bucket

DynamoDB

SQS Queue

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

Evet

Hayir

Evet

Hayir

Kullanici Auth Baslatir

Define Auth Challenge

Lambda

Create Auth Challenge

Lambda

Kullanici Yanit Verir

Verify Auth Challenge

Response Lambda

Token Ver?

Authentication Basarili

Daha Challenge?

Authentication Basarisiz

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

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

< 100

Kucuk Olcek

100-1000

Orta Olcek

> 1000

Enterprise

Custom Config

Per Tenant

Compliance

Isolation

Standard Config

Multi-Tenancy Pattern Sec

Tenant Sayisi

Shared User Pool

Custom Attributes

Shared Pool

Groups-Based

Enterprise

Gereksinimler?

Siloed User Pools

Tenant Basina Bir

Multi-Region

Shared Pools

Basit setup

Duesuk operasyonel maliyet

Tek config

Sinirli izolasyon

Daha iyi izolasyon

Group-based policy'ler

1000'lere olceklenebilir

10,000 groups limiti

Tam izolasyon

Tenant basina custom config

Compliance-ready

Yuksek operasyonel maliyet

Karmasik otomasyon

Cografi dagilim

Quota izolasyonu

Region'a gore compliance

Karmasik orkestrasyon

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

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

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

  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

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:

  • 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