Why is Cognito not migrating users from my Django RDS Aurora DB to Cognito User Pool?

0

I'm kinda new to Cognito, but needed to implement it for SSO between apps. For testing its implementation, I have a Django app, not in production, but with a Postgres RDS DB. Since for migrating the users, I need to check the password and Django hashes the passwords, it was necessary to access some of the Django methods. For doing that, I pushed a Docker image to an AWS ECR, which is run by a Lambda, triggered by the Cognito's User Pool's migration system. Inside that image, which has installed psycopg2 -for Postgres- and django, the code runs, checks the password, authenticates the django user, gets the data needed, updates the event["response"] of the lambda function, and returns the event. All seems to be working fine BUT the user is not migrated, getting the error message in the login page "Incorrect username or password.". I know nor the username nor the password are incorrect, since I've checked the logs and everything runs okay and the event is returning okay. I can't find the problem or the error to this.

Why isn't Cognito migrating the user if the lambda function runs okay and returns the event as it should without errors?

2 Answers
0

Hello.

Can I view users registered in the Cognito user pool?
If the user can be displayed, it is likely that the password migration from RDS has not been successful.
https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/list-users.html

The following documents may be useful for user migration.
https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html

profile picture
EXPERT
answered 8 months ago
  • Hello, sorry for the delay, but I've been reading those posts and trying some other possible solutions as checking the permission policies for the role, adding the password in the event['response']['userAttributes'], among other things, but it still doesn't migrate the user, and I don't understand why since the code doesn't throw any errors. I'll add the code I'm using below as an answer, so that you would have more context to determine is there's something wrong in my code (I know that adding the password might not be okay, but without that I get the same result too, I was trying)

  • As for your question, after the lambda finishes the process, I can't see the user that supposedly migrated in the user pool. But the users that were created directly using Cognito (not migration), those I can see in the User Pool.

0

This is the main.py that handles the migration when the AWS Lambda gets triggered when a user is signing in and he's not in the Cognito User Pool:

import json
from django.conf import settings
from django.contrib.auth.hashers import check_password
import boto3
import psycopg2
import uuid

# Set the PASSWORD_HASHERS setting to be called when authenticated. This is NECESSARY since we are working outside a Django project:
settings.configure(
    PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
    "django.contrib.auth.hashers.ScryptPasswordHasher",
],
    DEBUG=True,  # Set DEBUG to your desired value
)

def get_db_credentials(secret_name):
    session = boto3.session.Session()
    client = session.client(service_name='secretsmanager')
    
    response = client.get_secret_value(SecretId=secret_name)
    secret = json.loads(response['SecretString'])
    return secret

    
    
def authenticate_user(user_email, password, user_auth_table, db_data):
    try:
        connection = psycopg2.connect(
            dbname=db_data["DATABASE_NAME"],
            user=db_data["DATABASE_USER"],
            password=db_data["DATABASE_PASSWORD"],
            host=db_data["DATABASE_HOST"],
            port=db_data["DATABASE_PORT"]
        )

        cursor = connection.cursor()

        # Fetch hashed password and user's email from the database:
        cursor.execute(f"SELECT password, username, email, is_app1_user, is_app2_userFROM {user_auth_table} WHERE email = '{user_email}'")
        result = cursor.fetchone()
        
        user = {
            "email": result[2],
            "username": result[1],
            "password": password,
            "is_app1_user": result[3],
            "is_app2_user": result[4]
        }
        
        if not result:
            raise Exception("User not found")
        
        hashed_password = result[0]
        
        # Use Django's check_password to validate:
        if not check_password(password, hashed_password):
            raise Exception("Incorrect password")

        return {
            'statusCode': 200,
            'body': {
                'message': "Authentication successful",
                'user': user
            }
        }
        
    except Exception as e:
        print("Error during authentication:", e)
        return {
            'statusCode': 500,
            'body': {
                'error': str(e)
            }
        }
    finally:
        if connection:
            cursor.close()
            connection.close()


def lambda_handler(event, context):
    # Check the trigger source, for this we need the "UserMigration_Authentication" trigger
    print("EVENT: ", event)
    if event['triggerSource'] == 'UserMigration_Authentication':

        # Get the user name and password from the event (need to check wheter we need the Django's secret key or not)
        user_email = event['userName'] # userName comes from Cognito, so if we are using email, userName will be the email.
        password = event['request']['password']
        
        # We need the "client_id" to check whether the request is coming from App1 or App2:
        client_id = event['callerContext']['clientId']
        
        if client_id == 'cs40****2bj1v***************':
            app = 'app2'
        elif client_id == 'NEED_TO_CREATE_THE_OTHER_APP_CLIENT_ID':
            app = 'app1'
        
        # Checking in the logs it's accessing the right app:    
        print("APP: ", app)
            
        # Connect to the DB depending if the request comes from app1 or app2:
        if app == "app2":
            # Get the credentials from the AWS Secrets Manager
            db_credentials = get_db_credentials(secret_name='my_secret_name')
            print("DB USER: ", db_credentials['username'])
            db_data = {
                "DATABASE_NAME": '*******',
                "DATABASE_USER": db_credentials['username'],
                "DATABASE_PASSWORD": db_credentials['password'],
                "DATABASE_HOST": '********************************', # Here I used the Writer type
                "DATABASE_PORT": '****'
            }
            
            # Attempt to authenticate against the Django database:

            result = authenticate_user(user_email=user_email, password=password, user_auth_table="public.interface_customuser", db_data=db_data)
            
        elif app == "app1":
            # Here I'll need to setup the app1's db connection
            pass
            
            
        user = result["body"]["user"]
        
        if user:
            print("USER: ", user)
            
            # If authentication was successful, populate the user attributes for Cognito
            # Here, adjust the attribute names and fetch logic according to your model and requirements
            event['response']['userAttributes'] = {
                'username': user["username"],
                'password': user["password"],
                'email': user["email"],
                'email_verified': 'true',
                'custom:is_app1_user': 1, # This is because I've set these custom attributes as number (1 or 0)
                'custom:is_app2_user': 1
            }
            
            # If using Django's default password hasher, the first part of the encoded password is the used algorithm.
            event['response']['finalUserStatus'] = 'CONFIRMED'
            event['response']['messageAction'] = 'SUPPRESS'  # This ensures that Amazon Cognito won't send an SMS or email for verification
            event['response']['desiredDeliveryMediums'] = ['EMAIL']
            
            print(event)
            
            return event
            
        else:
            # If authentication fails:
            raise Exception(f"Not a {app} user or password incorrect")

And this is the Event it returns (I printed it to see) when checking the logs in Cloudwatch:

{'version': '1', 'triggerSource': 'UserMigration_Authentication', 'region': 'eu-west-2', 'userPoolId': 'eu-west-2_the_right_user_pool_id', 'userName': 'andres**********@gmail.com', 'callerContext': {'awsSdkVersion': 'aws-sdk-unknown-unknown', 'clientId': 'cs40****2bj1v***************'}, 'request': {'password': '***********', 'validationData': None, 'userAttributes': None}, 'response': {'userAttributes': {'username': 'andres**********', 'password': '***********', 'email': 'andres**********@gmail.com', 'email_verified': 'true', 'custom:is_app1_user': 1, 'custom:is_app2_user': 1}, 'forceAliasCreation': None, 'enableSMSMFA': None, 'finalUserStatus': 'CONFIRMED', 'messageAction': 'SUPPRESS', 'desiredDeliveryMediums': ['EMAIL']}}
answered 7 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions