aws-amplify / aws-sdk-ios

AWS SDK for iOS. For more information, see our web site:
https://aws-amplify.github.io/docs
Other
1.68k stars 884 forks source link

AWS Cognito MagicLinks usage #5399

Closed Sawdre closed 2 months ago

Sawdre commented 3 months ago

Good afternoon! I need your help.

I need to implement AWS Cognito Magic Link to authenticate the user. I made in-app request to send an email. I do receive email with token. I parse it in the app. How to proceed with it and pass this token to sdk to authenticate the user.

In response from SDK I am getting an error:

RespondToAuthChallenge Error: Error Domain=com.amazonaws.AWSCognitoIdentityProviderErrorDomain Code=21 "(null)" UserInfo={__type=NotAuthorizedException, message=Incorrect username or password.}

Not sure why password is there in MagicLink implementation, but username is correct.

I am doing the following: self.magicLinkToken // token parsed from the email

[self initiateAuthChallengeForUsername:self.user.username];

- (void)initiateAuthChallengeForUsername:(NSString *)username {
    AWSCognitoIdentityProvider *identityProvider = [AWSCognitoIdentityProvider defaultCognitoIdentityProvider];

    AWSCognitoIdentityProviderInitiateAuthRequest *initiateAuthRequest = [AWSCognitoIdentityProviderInitiateAuthRequest new];
    initiateAuthRequest.clientId = APMConfig.cognitoUserPoolAppClientId;  // Your Cognito User Pool App Client ID
    initiateAuthRequest.authFlow = AWSCognitoIdentityProviderAuthFlowTypeCustomAuth;
    initiateAuthRequest.authParameters = @{@"USERNAME": username};

    [[identityProvider initiateAuth:initiateAuthRequest] continueWithBlock:^id(AWSTask<AWSCognitoIdentityProviderInitiateAuthResponse *> *task) {
        if (task.error) {
            NSLog(@"InitiateAuth Error: %@", task.error);
            NSDictionary *errorDict = task.error.userInfo;
            for (NSString *key in errorDict) {
                NSLog(@"%@: %@", key, errorDict[key]);
            }
        } else {
            AWSCognitoIdentityProviderInitiateAuthResponse *authResponse = task.result;
            NSString *session = authResponse.session;

            if (session) {
                NSLog(@"InitiateAuth succeeded. Session: %@", session);
                [self respondToAuthChallengeWithToken:self.magicLinkToken username:username session:session];
            } else {
                NSLog(@"InitiateAuth succeeded but session is nil");
            }
        }
        return nil;
    }];
}

- (void)respondToAuthChallengeWithToken:(NSString *)magicLinkToken username:(NSString *)username session:(NSString *)session {
    AWSCognitoIdentityProvider *identityProvider = [AWSCognitoIdentityProvider defaultCognitoIdentityProvider];

    AWSCognitoIdentityProviderRespondToAuthChallengeRequest *challengeResponseRequest = [AWSCognitoIdentityProviderRespondToAuthChallengeRequest new];
    challengeResponseRequest.clientId = APMConfig.cognitoUserPoolAppClientId; // Your Cognito User Pool App Client ID
    challengeResponseRequest.challengeName = AWSCognitoIdentityProviderChallengeNameTypeCustomChallenge;
    challengeResponseRequest.session = session;
    challengeResponseRequest.challengeResponses = @{
        @"USERNAME": username,
        @"ANSWER": magicLinkToken // token parsed from the email
    };

    [[identityProvider respondToAuthChallenge:challengeResponseRequest] continueWithBlock:^id(AWSTask<AWSCognitoIdentityProviderRespondToAuthChallengeResponse *> *task) {
        if (task.error) {
            NSLog(@"RespondToAuthChallenge Error: %@", task.error);
            NSDictionary *errorDict = task.error.userInfo;
            for (NSString *key in errorDict) {
                NSLog(@"%@: %@", key, errorDict[key]);
            }
        } else {
            AWSCognitoIdentityProviderRespondToAuthChallengeResponse *challengeResponse = task.result;

            if (challengeResponse.authenticationResult) {
                NSLog(@"Logged in! Token: %@", challengeResponse.authenticationResult.idToken);
            } else {
                NSLog(@"RespondToAuthChallenge succeeded but no authenticationResult found");
            }
        }
        return nil;
    }];
}

I hope for your help as I don't have any ideas anymore. Thank you in advance!

5d commented 3 months ago

Hi @Sawdre ,

Before you start coding with aws-sdk-ios, have you checked using the AWS CLI to ensure that the magicLinkToken you received via email is actually functional?

Sawdre commented 3 months ago

hello @5d thank you for your response, yes I have this functionality working on android and token is valid there.

harsh62 commented 2 months ago

To help us debug further, would you be able to provide verbose logs when the error occurs. You can enable verbose logging to the console by doing this:

AWSDDLog.sharedInstance.logLevel = .verbose AWSDDLog.add(AWSDDTTYLogger.sharedInstance)

Furthermore, have you tried using AWS Amplify, which is has a lot a good Auth API's that make writing and handling logic very easy.

Sawdre commented 2 months ago

Okay, I found what the issue was. Actually, there were three issues:

  1. I forgot to decode the MagicLink token
  2. When you enter an email, it generates a user ID string, and this ID should be used as “USERNAME”. challengeResponseRequest.challengeResponses = @{ @"USERNAME": username, @"ANSWER": magicLinkToken // token parsed from the email };
  3. And the third issue is the most bizarre. The email address I was using was banned, blocked, or not whitelisted (I still not sure which one), so the Cognito SDK returned the error “Username or password is incorrect,” even after I replaced it with the ID from point 2.

Thank you @harsh62 and @5d for responding

Sawdre commented 2 months ago

See last comment

harsh62 commented 2 months ago

@Sawdre Did you try the same thing using AWS Amplify?

Sawdre commented 2 months ago

@harsh62 well it was my first intent indeed, but I did not find in the documentation that Amplify supports it.

harsh62 commented 2 months ago

@Sawdre

Here is how you can use the sign in API's:

    import Amplify
    import AWSCognitoAuthPlugin

    func magicLinkSignIn() async throws {
        let result = try await Amplify.Auth.signIn(username: "<username>", options: .init(pluginOptions: AWSAuthSignInOptions(authFlowType: .customWithoutSRP)))
        switch result.nextStep {
        case .confirmSignInWithCustomChallenge(let info):
            let signedInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "<your magic link response>")
        default:
            break
        }
    }

If you want to manually configure, you can use:

 func configureAmplify() {
        do {
            try Amplify.add(plugin: AWSCognitoAuthPlugin())
            let configuration = AmplifyConfiguration(
                auth: AuthCategoryConfiguration(
                    plugins: [
                        "awsCognitoAuthPlugin": [
                            "IdentityManager": [
                                "Default": [:]
                            ],
                            "CognitoUserPool": [
                                "Default": [
                                    "PoolId": "<userPoolId>",
                                    "Region": "<region>",
                                    "AppClientId": "<appClientId>"
                                ]
                            ]
                        ]
                    ]
                )
            )
            try Amplify.configure(configuration)
            Amplify.Logging.logLevel = .verbose
        } catch {
            print("Failed to initialize Amplify with \(error)")
        }
    }

Here is an example how you can also setup your lambdas. https://docs.amplify.aws/swift/build-a-backend/functions/examples/google-recaptcha-challenge/

The example shows google recaptcha challenge, but you can tweak the implementation to your own liking and pretty much do anything.

Sawdre commented 2 months ago

Aha! Thanks @harsh62 I will try