aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.31k stars 241 forks source link

Amplify Flutter Auth confirmSignIn() times out #4541

Open philyang07 opened 6 months ago

philyang07 commented 6 months ago

Description

Hi,

As part of our custom passwordless flow, we have reports from just a few of our customers that they are experiencing a timeout issue. From logs we were able to determine that the following code final result = await Amplify.Auth.confirmSignIn(confirmationValue: confirmationCode) .timeout(const Duration(seconds: 30)); return result.isSignedIn;

is leading to a timeout exception being thrown. Unfortunately, we are not able to replicate the issue on our end. Thus, we were hoping that you may have possible causes of such a timeout from occuring.

The interesting thing is that all the create, define and verify lambda triggers all seem to run smoothly. In fact, if we just catch the timeout error and let the user through, it seems as if they were successfully authenticated; the auth session seems to be completely fine.

Categories

Steps to Reproduce

No response

Screenshots

No response

Platforms

Flutter Version

3.16.9

Amplify Flutter Version

1.7.0

Deployment Method

Amplify CLI

Schema

No response

Equartey commented 6 months ago

Hi @philyang07, sorry to hear you're seeing timeouts.

Unfortunately, given the limited information we can't know what the issue is, especially when using custom sign in flow.

Without reproduction steps, the support we can provide will be limited.

Can you isolate where the timeout is coming from? I assume Cognito if the lambda is running smoothly?

philyang07 commented 6 months ago

Hi @Equartey thank you for your response. Yes, the lambda runs smoothly. It is just the line of code that I have supplied that causes the timeout. The lambda triggers return successfully, so I think it's something to do with Cognito. Possibly it could be to do with some session storage etc. or something but we can't get access to the specific browser information of our customers.

Equartey commented 6 months ago

Hi @philyang07, I do not think session storage or anything underlying like that would be the culprit here.

I see you've added a .timeout() to your api call, is this your fix to the observed timeout or is this triggering the timeout that you're catching?

If this is only happening on web, perhaps getting more browser data (ie network traffic) would be the next best steps to helping us resolve this issue.

philyang07 commented 6 months ago

The .timeout() throws the exception, as a result of the Amplify call not ever returning.

Yes, this is happening on web. Unfortunatley we are unable to get network traffic associated with the customer's browser at the moment.

Equartey commented 5 months ago

@philyang07 unfortunately, it is difficult to know the cause with the limited information. It could likely be related to the network condition of the device, ie network latency is slow enough to cause the timeout.

If you can gather more information of the network condition or reproduction steps we can continue to triage this issue.

Jordan-Nelson commented 5 months ago

@philyang07 - With the info provided, this appears to be due to network conditions. I am going to close this issue. If you are able to reproduce this or provide additional info we can open this back up and investigate. Thanks.

philyang07 commented 5 months ago

@Jordan-Nelson It is not a network conditions issue as catching the timeout exception and letting the user straight through seems to work completely fine; fetching the auth session returns a valid one. Anyways, I will report back once we have reproducibility/additional data from network traffic.

philyang07 commented 4 months ago

Hi @Jordan-Nelson and @Equartey, looking at CloudTrail logs I've found something a bit unusual:

Event 1:
{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "Unknown",
        "principalId": "Anonymous"
    },
    "eventTime": "2024-04-04T03:28:29Z",
    "eventSource": "cognito-idp.amazonaws.com",
    "eventName": "RespondToAuthChallenge",
    "awsRegion": "ap-southeast-2",
    "sourceIPAddress": "118.211.178.147",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
    "requestParameters": {
        "clientId": "2de106gfhtd9okjump755no6tl",
        "challengeName": "CUSTOM_CHALLENGE",
        "session": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "challengeResponses": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "analyticsMetadata": {
            "analyticsEndpointId": "d5fa2487-a27e-4c9a-b88c-dfa448210a1f"
        },
        "userContextData": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "clientMetadata": {}
    },
    "responseElements": {
        "challengeName": "DEVICE_SRP_AUTH",
        "session": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "challengeParameters": "HIDDEN_DUE_TO_SECURITY_REASONS"
    },
    "additionalEventData": {
        "sub": "26856675-409a-4ef4-9026-d49db0593c44"
    },
    "requestID": "0ab7649f-5f36-4d72-a193-658cc0e9d9f9",
    "eventID": "af5725bf-863c-4205-ad1a-02b2f08cd6fc",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "164832100909",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "cognito-idp.ap-southeast-2.amazonaws.com"
    }
}

Event 2:
{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "Unknown",
        "principalId": "Anonymous"
    },
    "eventTime": "2024-04-04T03:28:42Z",
    "eventSource": "cognito-idp.amazonaws.com",
    "eventName": "RespondToAuthChallenge",
    "awsRegion": "ap-southeast-2",
    "sourceIPAddress": "118.211.178.147",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
    "requestParameters": {
        "clientId": "2de106gfhtd9okjump755no6tl",
        "challengeName": "DEVICE_SRP_AUTH",
        "session": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "challengeResponses": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "analyticsMetadata": {
            "analyticsEndpointId": "d5fa2487-a27e-4c9a-b88c-dfa448210a1f"
        },
        "userContextData": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "clientMetadata": {}
    },
    "responseElements": {
        "challengeName": "DEVICE_PASSWORD_VERIFIER",
        "challengeParameters": "HIDDEN_DUE_TO_SECURITY_REASONS"
    },
    "additionalEventData": {
        "sub": "26856675-409a-4ef4-9026-d49db0593c44"
    },
    "requestID": "181c5dcb-fe03-4b70-9894-4bf697313203",
    "eventID": "14296d32-fff3-4f7b-8ad7-02cf569259e6",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "164832100909",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "cognito-idp.ap-southeast-2.amazonaws.com"
    }
}

Event 3:
{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "Unknown",
        "principalId": "Anonymous"
    },
    "eventTime": "2024-04-04T03:29:00Z",
    "eventSource": "cognito-idp.amazonaws.com",
    "eventName": "RespondToAuthChallenge",
    "awsRegion": "ap-southeast-2",
    "sourceIPAddress": "118.211.178.147",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
    "requestParameters": {
        "clientId": "2de106gfhtd9okjump755no6tl",
        "challengeName": "DEVICE_PASSWORD_VERIFIER",
        "challengeResponses": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "analyticsMetadata": {
            "analyticsEndpointId": "d5fa2487-a27e-4c9a-b88c-dfa448210a1f"
        },
        "userContextData": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "clientMetadata": {}
    },
    "responseElements": {
        "challengeParameters": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "authenticationResult": {
            "accessToken": "HIDDEN_DUE_TO_SECURITY_REASONS",
            "expiresIn": 14400,
            "tokenType": "Bearer",
            "refreshToken": "HIDDEN_DUE_TO_SECURITY_REASONS",
            "idToken": "HIDDEN_DUE_TO_SECURITY_REASONS"
        }
    },
    "additionalEventData": {
        "sub": "26856675-409a-4ef4-9026-d49db0593c44"
    },
    "requestID": "96ceca1b-33e9-41fe-a37d-8f8a5bed63fb",
    "eventID": "55b69f0d-452a-498f-98af-5c43f0fcd18b",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "164832100909",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "cognito-idp.ap-southeast-2.amazonaws.com"
    }
}

There seems to be 3 RespondToAuth calls for this particular user, and with challenges CUSTOM_CHALLENGE to DEVICE_SRP_AUTH to DEVICE_PASSWORD_VERIFIER.

Could this be causing the confirmSignIn call to fail? Perhaps it is not expecting the additional DEVICE_SRP_AUTH and DEVICE_PASSWORD_VERIFIER challenges from the response?

Normally, a RespondToAuth should look like

    "eventVersion": "1.08",
    "userIdentity": {
        "type": "Unknown",
        "principalId": "Anonymous"
    },
    "eventTime": "2024-04-16T04:37:05Z",
    "eventSource": "cognito-idp.amazonaws.com",
    "eventName": "RespondToAuthChallenge",
    "awsRegion": "ap-southeast-2",
    "sourceIPAddress": "210.84.54.88",
    "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/311.0.621658582 Mobile/15E148 Safari/604.1",
    "requestParameters": {
        "clientId": "2de106gfhtd9okjump755no6tl",
        "challengeName": "CUSTOM_CHALLENGE",
        "session": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "challengeResponses": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "analyticsMetadata": {
            "analyticsEndpointId": "12342f63-58f7-4c8e-806f-6ff9e72612ef"
        },
        "userContextData": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "clientMetadata": {}
    },
    "responseElements": {
        "challengeParameters": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "authenticationResult": {
            "accessToken": "HIDDEN_DUE_TO_SECURITY_REASONS",
            "expiresIn": 14400,
            "tokenType": "Bearer",
            "refreshToken": "HIDDEN_DUE_TO_SECURITY_REASONS",
            "idToken": "HIDDEN_DUE_TO_SECURITY_REASONS"
        }
    },
    "additionalEventData": {
        "sub": "9aedc4d8-255d-4812-8bb3-d48b87ee8b6a"
    },
    "requestID": "43551b18-0ead-42d3-a45f-b02e844c036d",
    "eventID": "aa901849-100f-4e23-bfe0-3e73dd091aa3",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "164832100909",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "cognito-idp.ap-southeast-2.amazonaws.com"
    }
}

Hopefully this provides some additional insight.

philyang07 commented 4 months ago

FYI I raised an issue and this seems somewhat still related to it https://github.com/aws-amplify/amplify-flutter/pull/3652.

NikaHsn commented 4 months ago

@philyang07 sorry that you are still facing this issue and thanks for providing more info. we will investigate and provide update as we have them.

NikaHsn commented 4 months ago

@philyang07 are the three event generated with a signle confirmSignin call? would it be possible to share the lambda code for the custom auth flow you have?

philyang07 commented 4 months ago

Hi @NikaHsn , I believe the 3 events would likely be generated with separate subsequent confirmSignin calls since they are around 10 seconds apart - probably upon subsequent attempts by the user to log in with their verification code.

The lambda code for the define auth trigger is

def define_auth_challenge(event: dict):
    request = event.get("request", {})
    session = request.get("session", [])

    # Flow initiated, present custom challenge
    if not session:
        return present_custom_challenge(event)

    challenge_name = session[-1].get("challengeName")
    if challenge_name == "SRP_A":
        return handle_password(event)
    elif challenge_name == "PASSWORD_VERIFIER":
        return handle_password_verification(event)
    return handle_custom_challenge(event)

def handle_custom_challenge(event: dict):
    request = event.get("request", {})
    session = request.get("session", [])
    response = event["response"]
    result = session[-1].get("challengeResult", False)

    if len(session) >= 3 and not result:
        response["issueTokens"] = False
        response["failAuthentication"] = True
    elif result:
        response["issueTokens"] = True
        response["failAuthentication"] = False
    else:
        response["challengeName"] = "CUSTOM_CHALLENGE"
        response["issueTokens"] = False
        response["failAuthentication"] = False

    return event

def handle_password(event: dict):
    request = event.get("request", {})
    user_attributes = request.get("userAttributes", {})
    response = event["response"]

    if request.get("userNotFound", False):
        response["challengeName"] = "NOT_FOUND"
    elif user_attributes.get("cognito:user_status") != "CONFIRMED":
        response["challengeName"] = "NOT_CONFIRMED"
    else:
        response["challengeName"] = "PASSWORD_VERIFIER"

    response["issueTokens"] = False
    response["failAuthentication"] = False

    return event

def handle_password_verification(event: dict):
    session = event.get("request", {}).get("session", [])
    assert len(session) > 0

    result = session[-1].get("challengeResult", False)
    response = event["response"]
    response["issueTokens"] = result
    response["failAuthentication"] = not result

    return event

def present_custom_challenge(event: dict):
    response = event["response"]
    response["issueTokens"] = False
    response["failAuthentication"] = False
    response["challengeName"] = "CUSTOM_CHALLENGE"

    return event

def handler(event: dict, context):
    if debug_mode:
        print(json.dumps(event))

    return define_auth_challenge(event)
philyang07 commented 4 months ago

Hi @NikaHsn, was just checking to see if you have made any progress with this?

khatruong2009 commented 4 months ago

Hi @philyang07, thank you for providing these details. We will continue investigating and get back to you as soon as we have any updates. Thanks.