Amazon Cognito MFA Implementation Guide

Implement multi-factor authentication with Cognito using TOTP, SMS, and adaptive authentication for enhanced security.

Multi-factor authentication (MFA) is essential for protecting user accounts. Amazon Cognito provides flexible MFA options including TOTP, SMS, and adaptive authentication.

Configuring User Pool MFA

Enable MFA Settings

CognitoUserPool:
  Type: AWS::Cognito::UserPool
  Properties:
    UserPoolName: SecureUserPool
    MfaConfiguration: OPTIONAL  # or REQUIRED
    EnabledMfas:
      - SOFTWARE_TOKEN_MFA
      - SMS_MFA
    SmsConfiguration:
      SnsCallerArn: !GetAtt CognitoSNSRole.Arn
      ExternalId: cognito-sms-external-id
    SoftwareTokenMfaConfiguration:
      Enabled: true
    AccountRecoverySetting:
      RecoveryMechanisms:
        - Name: verified_email
          Priority: 1
        - Name: verified_phone_number
          Priority: 2

User Pool Client Configuration

UserPoolClient:
  Type: AWS::Cognito::UserPoolClient
  Properties:
    ClientName: WebAppClient
    UserPoolId: !Ref CognitoUserPool
    ExplicitAuthFlows:
      - ALLOW_USER_SRP_AUTH
      - ALLOW_REFRESH_TOKEN_AUTH
    PreventUserExistenceErrors: ENABLED
    EnableTokenRevocation: true
    AuthSessionValidity: 3

TOTP MFA Implementation

Associate TOTP

import boto3

cognito = boto3.client('cognito-idp')

def setup_totp_mfa(access_token):
    # Get secret code for authenticator app
    response = cognito.associate_software_token(
        AccessToken=access_token
    )
    
    secret_code = response['SecretCode']
    session = response.get('Session')
    
    # Generate QR code URI for authenticator apps
    qr_uri = f"otpauth://totp/MyApp:user@example.com?secret={secret_code}&issuer=MyApp"
    
    return {
        'secret_code': secret_code,
        'qr_uri': qr_uri,
        'session': session
    }

def verify_totp_setup(access_token, user_code, friendly_name='Authenticator'):
    response = cognito.verify_software_token(
        AccessToken=access_token,
        UserCode=user_code,
        FriendlyDeviceName=friendly_name
    )
    
    if response['Status'] == 'SUCCESS':
        # Set MFA preference
        cognito.set_user_mfa_preference(
            AccessToken=access_token,
            SoftwareTokenMfaSettings={
                'Enabled': True,
                'PreferredMfa': True
            }
        )
        return True
    return False

TOTP Authentication Flow

def authenticate_with_mfa(username, password, user_pool_id, client_id):
    # Initial authentication
    response = cognito.initiate_auth(
        ClientId=client_id,
        AuthFlow='USER_SRP_AUTH',
        AuthParameters={
            'USERNAME': username,
            'SRP_A': calculate_srp_a()
        }
    )
    
    # Handle MFA challenge
    if response.get('ChallengeName') == 'SOFTWARE_TOKEN_MFA':
        mfa_code = get_user_mfa_code()  # Get from user
        
        response = cognito.respond_to_auth_challenge(
            ClientId=client_id,
            ChallengeName='SOFTWARE_TOKEN_MFA',
            Session=response['Session'],
            ChallengeResponses={
                'USERNAME': username,
                'SOFTWARE_TOKEN_MFA_CODE': mfa_code
            }
        )
    
    return response.get('AuthenticationResult')

Adaptive Authentication

Advanced Security Features

UserPool:
  Type: AWS::Cognito::UserPool
  Properties:
    UserPoolAddOns:
      AdvancedSecurityMode: ENFORCED
    LambdaConfig:
      PreAuthentication: !GetAtt PreAuthLambda.Arn
      PostAuthentication: !GetAtt PostAuthLambda.Arn
      DefineAuthChallenge: !GetAtt DefineChallengeLambda.Arn
      CreateAuthChallenge: !GetAtt CreateChallengeLambda.Arn
      VerifyAuthChallengeResponse: !GetAtt VerifyChallengeLambda.Arn

Risk-Based Authentication Lambda

def pre_authentication_handler(event, context):
    user_context = event.get('request', {}).get('userContextData')
    
    risk_level = assess_risk(
        ip_address=user_context.get('ipAddress'),
        device_fingerprint=user_context.get('encodedData'),
        user_agent=event.get('request', {}).get('validationData', {}).get('User-Agent')
    )
    
    if risk_level == 'HIGH':
        event['response']['autoConfirmUser'] = False
        event['response']['autoVerifyPhone'] = False
        # Force MFA for high-risk authentications
    
    return event

def assess_risk(ip_address, device_fingerprint, user_agent):
    risk_score = 0
    
    # Check IP reputation
    if is_tor_exit_node(ip_address) or is_vpn(ip_address):
        risk_score += 30
    
    # Check device
    if not is_known_device(device_fingerprint):
        risk_score += 20
    
    # Check location
    if is_unusual_location(ip_address):
        risk_score += 25
    
    # Check time
    if is_unusual_time():
        risk_score += 15
    
    if risk_score >= 50:
        return 'HIGH'
    elif risk_score >= 25:
        return 'MEDIUM'
    return 'LOW'

Device Tracking

def remember_device(access_token, device_key, device_name):
    cognito.confirm_device(
        AccessToken=access_token,
        DeviceKey=device_key,
        DeviceSecretVerifierConfig={
            'PasswordVerifier': calculate_device_verifier(),
            'Salt': generate_salt()
        },
        DeviceName=device_name
    )
    
    # Update device status
    cognito.update_device_status(
        AccessToken=access_token,
        DeviceKey=device_key,
        DeviceRememberedStatus='remembered'
    )

Working with Warqline

We are a cloud engineering consultancy and an official AWS and Google Cloud partner. If you are running this in production and want a second pair of eyes, we scope work in a free 45-minute technical call: you describe what you are running and what worries you, and we tell you what we would look at first.

Talk to an engineer

Conclusion

Cognito MFA provides flexible, secure authentication options. Combine TOTP, adaptive authentication, and device tracking for comprehensive user protection.