16

I'm trying to implement the new AWS Cognito User Pools in my iOS (Swift) app, but I'm struggling to get the sign in process to work. I am essentially trying to follow the example available here.

This is what I have so far:

AppDelegate:

class AppDelegate: UIResponder, UIApplicationDelegate, AWSCognitoIdentityInteractiveAuthenticationDelegate {
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        let serviceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: nil)
        AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = serviceConfiguration
        let configurationUserPool = AWSCognitoIdentityUserPoolConfiguration(
            clientId: "###",
            clientSecret: "#########",
            poolId: "###")
        AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: configurationUserPool, forKey: "UserPool")
        self.userPool = AWSCognitoIdentityUserPool(forKey: "UserPool")

        self.userPool!.delegate = self

        return true
    }

    func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
        let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        let logInNavigationController = mainStoryboard.instantiateViewControllerWithIdentifier("LogInNavigationController") as! UINavigationController

        dispatch_async(dispatch_get_main_queue(), {
            self.window?.rootViewController = logInNavigationController
        })

        let logInViewController = mainStoryboard.instantiateViewControllerWithIdentifier("LogInViewController") as! LogInViewController
        return logInViewController
    }
}

LogInViewController:

class LogInViewController: UIViewController, AWSCognitoIdentityPasswordAuthentication {
    var usernameText : String?
    var passwordAuthenticationCompletion = AWSTaskCompletionSource()

    func getPasswordAuthenticationDetails(authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource) {
        self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource

        dispatch_async(dispatch_get_main_queue(), {
            if self.usernameText == nil {
                self.usernameText = authenticationInput.lastKnownUsername
            }
        })
    }

    func didCompletePasswordAuthenticationStepWithError(error: NSError) {
        dispatch_async(dispatch_get_main_queue(), {
            let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
            let mainNavigationController = mainStoryboard.instantiateViewControllerWithIdentifier("MainNavigationController") as! UINavigationController
            (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController = mainNavigationController
        })
    }

    func logInButtonPressed() {
        self.passwordAuthenticationCompletion.setResult(AWSCognitoIdentityPasswordAuthenticationDetails(username: emailTextField.text, password: passwordTextField.text))
    }
}

Nothing seems to happen when I hit the log in button, although if I hit it again I get an NSInternalInconsistencyException (which I believe is because the AWSTask result has already been set).

Any help with this would be appreciated. I am using the AWS SDK for iOS version 2.4.1.

UPDATE:

Not a solution to my original problem, but I've been able to get User Pools working by using the explicit sign in method rather than the delegate method (see this page for details). Here is the code from my SignInViewController:

class SignInViewController: UIViewController {
    @IBAction func signInButtonTouched(sender: UIButton) {
        if (emailTextField.text != nil) && (passwordTextField.text != nil) {
            let user = (UIApplication.sharedApplication().delegate as! AppDelegate).userPool!.getUser(emailTextField.text!)
            user.getSession(emailTextField.text!, password: passwordTextField.text!, validationData: nil, scopes: nil).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: {
                (task:AWSTask!) -> AnyObject! in

                if task.error == nil {
                    // user is logged in - show logged in UI
                } else {
                    // error
                }

                return nil
            })
        } else {
            // email or password not set
        }
    }
}

Then, to consume an AWS service (which in my case is located in a different region to Cognito) I have created a new Credentials Provider using the User Pool:

let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: "###", identityProviderManager: (UIApplication.sharedApplication().delegate as! AppDelegate).userPool!)
let serviceConfiguration = AWSServiceConfiguration(region: .APNortheast1, credentialsProvider: credentialsProvider)
AWSLambdaInvoker.registerLambdaInvokerWithConfiguration(serviceConfiguration, forKey: "Lambda")
let lambdaInvoker = AWSLambdaInvoker(forKey: "Lambda")

One additional issue is that I was seeing this error each time I launched the app: "Could not find valid 'AWSDefaultRegionType', 'AWSCognitoRegionType', and 'AWSCognitoIdentityPoolId' values in info.plist.". This seems to be related to Fabric, which I am using to track crashes. I've solved this by changing this line in the AppDelegate:

Fabric.with([AWSCognito.self, Crashlytics.self])

to this:

Fabric.with([Crashlytics.self])

I hope this helps someone else.

Elliot
  • 415
  • 2
  • 5
  • 13

11 Answers11

18

Update 6: (and really final this time )

It is worth mentioning that (finally) AWS has made the AWS Mobile Hub build a very nice demo app that INCLUDES User Pools as a SignInProvider (With Google and Facebook too). The architecture is (in my opinion) excellent (they have separated Identity Management and getting credentials from Authentication) Check it out

Update 5: (and final)

There is a fairly complete example implementation, and some documentation of how it works in this other answer.

iOS - AWS MobileHub sign in with developer authenticated provider

Update 4:

If you want to get access to AWS services, there are more steps needed

It turns out that this does not get you authenticated with Cognito Federated Identities (the "logins" count on the identity browser remains at 0). To fix this you need to establish a credentialsProvider and do "credentialsProvider.getIdentityId". After that logins will show positive, and you can get services from AWS based upon your authenticated role.

If you are trying to do both Authenticated and UnAuthenticated access for your mobile app, then you need to create an AWSAnonymousCredentialsProvider (in a separate service configuration). Then you self.credentialsProvider?.invalidateCachedTemporaryCredentials() and self.credentialsProvider?.clearCredentials() when logging out and do the getidentityid again with the anonymous service configuration and you will get an anonymous id. (Note: I found it seemed like if you clearkeychain on the credentialsProvider it starts with a new id each time a user logs out, which could burn up your free 50,000 ids pretty quick. )

Update 3:

Uploaded a github sample app for AWS User Pools for IOS in Swift.

https://github.com/BruceBuckland/signin

Update 2:

I finally got AWS User Pools to work correctly in Swift

My problem was that each time the authentication start happened it was caused by an authentication failure in a different viewcontroller (my error). I ended up with a bunch of them running waiting for completion returns which never came and the API was "silent" (showed no errors). The API does not notice that it is being initiated multiple times ( by a different viewController each time) so it silently lets log in over and over. There is not enough of your code in the original post to see if you are having that same issue.

You have to be careful, the AWS sample code (in Objective-C) has two navigation controllers, and the code re-uses them. I don't like the way the sample app flashes the logged in view controller before the authentication delegate gets going and I was trying to improve that in the swift version and that caused my problem.

AWS User Pools API is set up to work with a storyboard or app structure that works like this:

1) Your app ASSUMES it is logged in, and then triggers the delegate which triggers authentication and the login screens if it is not.

2) In original logged in view controller pool.currentUser() is NOT enough to get the authentication going, API will only trigger the delegate when you do more (in my case user.getDetails()).

3) The authentication is completed through the didCompletePasswordAuthenticationStepWithError. This delegate method is called if you get an authentication (or other) error AND if you SUCCESSFULLY authenticate. In the case of successful authentication the NSError is nil, so it should be declared as NSError? in the delegate (this causes a warning). The API is beta, they will probably fix this.

4) One other little “gotcha”, it may be obvious to you, it caught me, when you define your User Pool in the console you specify allowed apps, and each of these apps HAS DIFFERENT STRINGS for Client ID strings. ( I was just plugging the same thing into the example) which works badly (but does not report errors). The API needs some work in the reporting department. It is very Verbose when it is working, but says nothing if you pass it the wrong Client Strings. Also it seems to say nothing if you (like I did) call the API from different viewcontrollers. It was just taking each new authentication request from a different viewcontroller and saying nothing.

Anyway, it works now. I hope this helps resolve your issue.

Update:

I finally got getPasswordAuthenticationDetails to execute.

It turns out it does not get executed until user.getDetails for the current user (even if there is no current user).

So

let user = appDelegate.pool!.currentUser() let details = user!.getDetails()

will result in the getPasswordAuthenticationDetails callback getting executed on the second line.

It seems the AWS UserPool concept is that we write an app that assumes we have a logged in user. We get details from that user (for instance in the initial view controller) and the delegate gets kicked off if we don't have a user.

The AWS documentation for User Pools on IOS is missing some important concept pages. Those pages ARE included in the (otherwise parallel) Android documentation. I admit that I am still struggling (days now) with getting User Pools to work in swift, but reading the "Main Classes" and "Key Concepts" Parts of the Android documentation clarified a lot for me. I can't see why it was omitted from the IOS doc.

Community
  • 1
  • 1
Bruce0
  • 2,718
  • 1
  • 17
  • 21
  • This is the closest I've seen to an answer to the original question, so I'm going to mark it as accepted. Thank you. – Elliot Jul 16 '16 at 06:12
  • Swift 3 and the sample app don't mix. 49 errors, and I simply can't get it to run. Some Swift 3 errors, others that I've never heard of and have no idea how to fix. Always crashing. – SomeGuy Jan 23 '17 at 15:03
9

Just adding my 2 cents for people who work with Objective-c and the sample app CognitoYourUserPoolsSample provided by Amazon. @Bruce0 already covered everything with his swift solution. But if you're running into this problem where getPasswordAuthenticationDetails doesn't get called when you hit sign-in is because you're not calling [self.user getDetails] at all. Indeed - getDetails triggers getPasswordAuthenticationDetails. If you look closer in the AWS sample app, they call it right when they launch the app in viewDidLoad of the UserDetailTableViewController, which is the first controller that gets loaded. If the user is not signed-in then getDetails response somehow triggers the SignInViewController. This is what I will explain below. It's like a "myHomeViewController" kind of, where you want to display user related info. Otherwise, you want to display the login/sign-up screen by default.

  • As a general rule of thumb, connect and init the Cognito User Pool in your AppDelegate (didFinishLaunchingWithOptions) exactly like they did in the sample app. Make sure to add the AWSCognitoIdentityInteractiveAuthenticationDelegate and implement startPasswordAuthentication where you'll bring up your sign-in ViewController. Let the AppDelegate take care of the WHAT_TO_DO_IF_USER_NOT_SIGNED_IN (e.g. bring the SignInViewController on top) then focus on the WHEN_DOES_THE_USER_NEEDS_TO_SIGNIN somewhere in your app.

  • When you need user specific data then tell the App that it's time to check if the user is signed-in (self.user getDetails). Again, if user isn't signed-in then the AppDelegate knows what to do. It over-rules the app and displays the sign-in View on top of everything. So, it could be right at the beginning (e.g. Facebook, Twitter, etc.) or somewhere else (e.g. Ebay, etc.). Simply call [self.user getDetails] at the end of viewDidLoad. This will prevent the current ViewController to show prior to the authentication step (sign-in/sign-up) OR simply load the current ViewController if the user is already signed-in.

When using AWS User Pool feature in your app, follow those steps:

  1. Find out where you need user specific data in any of YourViewControllers
  2. in the related ViewDidLoad, call [self.user getDetails]
  3. if User is signed-in already then display user specific data using the completionHandler of self.user getDetails.
  4. if not then startPasswordAuthentication gets automatically called in the AppDelegate, which brings up the sign-in ViewController before displaying YourViewController since you need user specific data
  5. User sign-in or sign-up
  6. dissmiss sign-in/sign-up ViewController and there you are back into YourViewController which can now be loaded with some user specific data.

The AWS sample App isn't straight forward but it is really simple.

Ed.
  • 3,923
  • 1
  • 15
  • 18
  • 1
    You saved my life. I wonder why your answer not voted. Thank you so much <3 – Tà Truhoada Jan 18 '17 at 12:03
  • Hey @Foo i refer AWS sample code and my app have same flow of sign in and if already logged in then redirect to home. But i need to check some info while user tried to login and this method called didCompletePasswordAuthenticationStepWithError. I called [self.user getDetails]; method into didCompletePasswordAuthenticationStepWithError, but unable to get response. In shot how can i get user detail while completion of this method? – Mitesh Dobareeya Jan 20 '17 at 13:20
  • @Mitesh Dobareeya - I see 2 problems in your setup. First, the method name "getDetails" is actually confusing because it does 2 things: user authentication and then returns the user details if authentication is successful. So as soon as you call [self.user getDetails], it's too late for you to check your user info. You need to check user info right before calling getDetails. Second problem, the method didCompletePasswordAuthenticationStepWithError is the delegate method called when [self.user getDetails] returns. So you can't call [self.user getDetails] inside of its own completion handler. – Ed. Jan 20 '17 at 17:08
  • @Foo now i set up my code as sample demo of cognitoUserPool and in didCompletePasswordAuthenticationStepWithError they dismissing the SignInController if authentication successful. And that place i need to check so how i can get data AWSCognitoIdentityUserGetDetailsResponse * response in this response object. – Mitesh Dobareeya Jan 21 '17 at 05:03
4

Thanks Elliot. I am trying to write the swift version of this code for few days now.

I tried using the explicit signIn using below code.

@IBAction func signInButtonPressed(sender: AnyObject) {

var emailTextField = "username"
var passwordTextField = "password"

let serviceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: nil)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = serviceConfiguration
let configurationUserPool = AWSCognitoIdentityUserPoolConfiguration.init(clientId: "####", clientSecret: "#####", poolId: "#####")
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: configurationUserPool, forKey: "TestUserPool")

let userPool = AWSCognitoIdentityUserPool(forKey: "TestUserPool")
let user = userPool.getUser(emailTextField)

user.getSession(emailTextField, password: passwordTextField, validationData: nil, scopes: nil).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: {
    (task:AWSTask!) -> AnyObject! in

    if task.error == nil {
            print("No Error")
            print(task.result)
    } else {
            print("Some Error")
            print(task.error)
    }
    return nil
})
}

When I provide correct credentials it goes to the Error Block. A verfication code is sent to my mobile each time I run the code although I have already verified my user during sign up process. Response Body is

Response body:
{"AuthState":"H4sIAAAAAAAAAAXB3ZJzMAAA0EeqBDvTnfkulhVCpRXyI3dNmI8KzU7bLZ5+z+m3HBjINz2jp6rxB174rmT+agWweHyPLVydEqFXi2o8j9gjTT6XcH1qeA+vWWQVbAMDW6gXvhEYgHOMH3gmg06pNTP61pBaNvO1E3zvEPFaSS2+3ccuQ6qUVvXcYjqBQKFoKvfoJHgLDKJx3VhlkKsIUGs7qbhH6qXZ3a9kl+v0uPEEOWqR0/7gk4T8iiYPm0XBXt59LivPwAGUmSr1RAfDqSz8COhkZcQLFdsev3oGVw3oWTRRXIHuRkTuqYS6/juHBIYRgzTsZ1crqHB5I5OZ2JvaMmB2aKqpS2qYizMqg5KjgqI24DtNGLfXenGu8/+/zU5ZnZlVCXTRNwtKxgXP2k0LJK9T58TCnxxRJtLnQ7AAFD4lZpnWk+dY4fGBCFqZlP4YyUGfqVQ3rW/i/PgJPnd8WN8fw/Hr5D0OChfhfCleb290yaV/AXf4itllINJONfv3B7RgGQzfAQAA","CodeDeliveryDetails":    
{"DeliveryMedium":"SMS","Destination":"+*******8869"}}
Some Error
Optional(Error Domain=com.amazonaws.AWSCognitoIdentityProviderErrorDomain Code=-1000 "startMultiFactorAuthentication not implemented by authentication delegate" UserInfo={NSLocalizedDescription=startMultiFactorAuthentication not implemented by authentication delegate})

When I provided wrong password the response body is

Response body:
{"__type":"NotAuthorizedException","message":"Incorrect username or password."}
Some Error
Optional(Error Domain=com.amazonaws.AWSCognitoIdentityProviderErrorDomain Code=12 "(null)" UserInfo={__type=NotAuthorizedException, message=Incorrect username or password.})

Could you suggest what I am doing wrong here?

Abhishek Kumar
  • 183
  • 2
  • 9
2

I've also followed the same steps mentioned in the original posters question, but the app never switches to the Login screen on startup. I've verified that the code to switch views is correct by placing directly in the AppDelegate.application(....) method. It appears that the startPasswordAuthentication(...) delete method is never called. Can someone post a link to a sample app that switches to the login screen using the AWSCognitoIdentityInteractiveAuthenticationDelegate protocol?

ranji
  • 51
  • 5
2

Folks integrating Cognito in iOS is tricky, especially when you're a newbie like me, well, the above answers are correct but if you're still struggling or stuck into the implementation part than this answer might help.

I am assuming a scenario where MFA or Simple Login is included.

So this is what my CognitoUserManagerClass looks like.

 class CognitoUserManager: NSObject {

static var shared = CognitoUserManager()

var pool : AWSCognitoIdentityUserPool?

func awsConfig(){
    let configuration = AWSServiceConfiguration(region: Constants.AWSRegionName, credentialsProvider: nil)
    let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.AWSClientId,
                                                                    clientSecret: Constants.AWSClientSecret,
                                                                    poolId: Constants.AWSPoolId)
    AWSCognitoIdentityUserPool.register(with: configuration, userPoolConfiguration: poolConfiguration, forKey: "POOL_NAME")
}


func getPoolFromAWS(name: String){
    pool = AWSCognitoIdentityUserPool(forKey: name)
}

func clearDataAndSignout(){
     //This will clear all the data stored in Keychain or user session. 
    pool?.clearAll()
}

I preferred a separate class for all my Cognito related configs.

Once Application launched. Call this in didFinishLaunch function in AppDelegate.

    CognitoUserManager.shared.awsConfig()
    CognitoUserManager.shared.getPoolFromAWS(name: "POOL_NAME")

Now, First Step Would be to check if the user is logged in or not?

Step 1. After didFinishLaunch I called my InitialViewController Where I decide to send it to HOME SCREEN or LOGIN SCREEN based on the tokens present in KeyChain.

NOTE: When you logged in successfully by any method via Cognito in iOS, Cognito will itself take care of saving the tokens in Keychain.

 class InitialViewController: UIViewController {

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(true)

    CognitoUserManager.shared.pool!.currentUser()?.getSession().continueWith { task in
        if let error = task.error as NSError?{
            //Print ERROR
            self.login()
        }else{
            guard let accessToken = task.result?.accessToken?.tokenString else {
                self.login()
                return nil
            }
            self.home()
        }
        return nil
    }
  }
}

Step 2. I took the user email and password on the login screen, and send it to the OTP(MFA) screen and call the getSession(with email and pass) on MFA Auth Screen, to avoid sending multiple OTP to the user. It was based on the design requirement, you can pick your way.

Step 3. My MFA Auth Screen Where I am doing all the login related Stuff looks something like this.

 class OTPViewController : UIViewController {


var mfaCodeCompletionSource: AWSTaskCompletionSource<NSString>?
var user: AWSCognitoIdentityUser?

override func viewDidLoad() {
    super.viewDidLoad()

    CognitoUserManager.shared.pool?.delegate = self

    guard let email = emailText, let pass = passwordText else {return}
    user = CognitoUserManager.shared.pool?.getUser(email)
    //HERE IF YOU'RE USING SIMPLE USER POOL WITH MFA THEN YOU'LL BE DIRECTED TO HOME SCREEN AND getSession() will return tokens, but if you're using MFA then it will trigger and send OTP on your Mobile.
    user?.getSession(email, password: pass, validationData: nil).continueWith(block: { (task) -> Any? in
        if let error = task.error as NSError?{
            //PRINT ERROR
        }else{
            let accessToken = task.result?.accessToken?.tokenString
            print(accessToken)
            self.sendToHome()
        }

        return nil
    })  
}

//GETTING OTP FROM TEXT FIELD AND SETTING IT.
@IBAction func SubmitOTPButtonPressed(_ sender: Any) {
    guard let otp = securtiyCodeTextfield.text else {return}
    self.mfaCodeCompletionSource?.set(result: otp as NSString)
}

}

  extension OTPViewController : AWSCognitoIdentityInteractiveAuthenticationDelegate{

func startMultiFactorAuthentication() -> AWSCognitoIdentityMultiFactorAuthentication {
    //THIS WILL CALLED IF YOU'RE USING MFA.
    return self
}

func startNewPasswordRequired() -> AWSCognitoIdentityNewPasswordRequired {
    //Called for first time to change the temporary Password.
    return self
}

func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
    return self
}

 }


  extension OTPViewController : AWSCognitoIdentityNewPasswordRequired, AWSCognitoIdentityPasswordAuthentication, AWSCognitoIdentityMultiFactorAuthentication{

func getCode(_ authenticationInput: AWSCognitoIdentityMultifactorAuthenticationInput, mfaCodeCompletionSource: AWSTaskCompletionSource<NSString>) {
    //MFA
    self.mfaCodeCompletionSource = mfaCodeCompletionSource
}

func didCompleteMultifactorAuthenticationStepWithError(_ error: Error?) {
    print(error.debugDescription)
}

func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
}

func didCompleteStepWithError(_ error: Error?) {
    print(error.debugDescription)
}

func getNewPasswordDetails(_ newPasswordRequiredInput: AWSCognitoIdentityNewPasswordRequiredInput, newPasswordRequiredCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>) {
    //SEND TO SETNEWPASSWORD CONTROLLER TO SET NEW PASSWORD
}
func didCompleteNewPasswordStepWithError(_ error: Error?) {
    print(error.debugDescription)
}

}

If you feel like this answer requires editing, please go ahead.

Shanu Singh
  • 365
  • 3
  • 16
1

It seems like the method "startMultiFactorAuthentication" is not implemented in the delegate, thats why incorrect password is getting detected, but when corrected password is given, it is escalated to MFA, but start MFA function is not found in the delegate and hence the login fails.

Hardik Shah
  • 916
  • 1
  • 7
  • 13
0

For me I kept getting this error because I have verification on, but I did NOT verify the user before attempting to login. Once it was verified everything worked.

0

I am following the same example and dealing with the same problem, and I haven't completely managed to solve it, but I strongly suspect the problem has something to do with this function never executing:

func getPasswordAuthenticationDetails(authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource) {
    self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
    dispatch_async(dispatch_get_main_queue(), {() -> Void in
        if self.usernameText == nil{
            self.usernameText = authenticationInput.lastKnownUsername
        }
    })
}

I have tried putting breakpoints and print statements in this method and it never seems to be activated. I would suggest doing the same in your code, since it sounds like your issue is identical to mine. I looked in the example and was unable to find a place where the method was called manually. I noticed that in your code you intialize the value of passwordAuthenticationCompletion like so:

var passwordAuthenticationCompletion = AWSTaskCompletionSource()

It seems as though getPasswordAuthenticationDetails() is supposed to be called before this line in the login method uses the relevant value: self.passwordAuthenticationCompletion.setResult(AWSCognitoIdentityPasswordAuthenticationDetails(username: emailTextField.text, password: passwordTextField.text))

I've been stuck for a while now trying to get any further than this, but I still think this is the correct route to implementing user registration/login. It's possible that some of the code in the example doesn't translate cleanly to Swift, and so some important function isn't being triggered as a result. I'll keep looking into it and update my answer if I confirm a solution.

user3726962
  • 333
  • 1
  • 4
  • 17
0

I've followed the same steps mentioned earlier using Swift and I noticed that the didCompletePasswordAuthenticationStepWithError is never called, although LogInViewController extends AWSCognitoIdentityPasswordAuthentication.

Also, startPasswordAuthentication() is not called in the delegate even though the delegate also implements AWSCognitoIdentityInteractiveAuthenticationDelegate.

I wonder if this is a problem with the Swift implementation since the sample Objective-C that Amazon provides has all these working.

Nirupa
  • 787
  • 1
  • 8
  • 20
  • Hopefully this will benefit someone who's going thru the same issue.. I was able to pass this hurdle by ensuring that the initial view controller is the `UserDetailTableViewController`, which then calls its "refresh" method, which in turn results in the invokation of `startPasswordAuthentication()` in the AppDelegate. But `didCompletePasswordAuthenticationStepWithError` is still not called after I Sign In on the `LogInViewController` (which is my `SignInViewController`). I also noticed that in the ObjC sample sends an http request is sent out through `AWSURLSessionManager.m` but not in Swift. – user3349763 Jul 17 '16 at 09:01
0

I think there is problem in this :

Object-c ->

self.passwordAuthenticationCompletion.result = [[AWSCognitoIdentityPasswordAuthenticationDetails alloc] initWithUsername: username password:password]; 

Swift -> self.passwordAuthenticationCompletion.setResult(AWSCognitoIdentityPasswordAuthenticationDetails.init(username: username, password: password))

and because there is something wrong in the previous statement , the "didCompletePasswordAuthenticationStepWithError" method did not fire.

I tried many things without luck :( - I tried to implement every thing in Swift (not Work) - I tried to add the Object-C files to my Swift based project (not Work)

So, I think I will use the orginal sample as my starter for my project.

Update:

I implement the SignInViewController & MFAController in Swift and import these Swift files in Object-C based project. and it is work fine ! So, now I am sure there issue or bug when we try to implement "AWSCognitoIdentityPasswordAuthentication" && "AWSCognitoIdentityMultiFactorAuthentication" protocols in Swift based project. The only solution I found is to go with Object-c based project.

Alharbi
  • 1
  • 1
  • Also, I have to mention that When I tried explicit authentication by GetSession, it is working fine without any issue. but I think it does not support MFA, because it is working fine if I disable MFA, and did not work if I enable MFA... So, now I am trying to use (AWSCognitoIdentityPasswordAuthenticationDetails.init) and implement the protocol because I enable the MFA. – Alharbi Aug 18 '16 at 08:29
0

This is how I made it happen in Swift, with a single ViewController and without setting anything up in the AppDelegate:

class LoginViewController: UIViewController, AWSCognitoIdentityInteractiveAuthenticationDelegate,  AWSCognitoIdentityPasswordAuthentication  {

        var passwordAuthenticationCompletion = AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>()
        var pool = AWSCognitoIdentityUserPool.init(forKey: "UserPool")

        override func viewDidLoad() {
                super.viewDidLoad()
                //setup service configuration
                let serviceConfiguration = AWSServiceConfiguration.init(region: .USEast1, credentialsProvider: nil)
                //create and config a pool
                let configuration = AWSCognitoIdentityUserPoolConfiguration.init(clientId: "YourClientId", clientSecret: "YourClientId", poolId: "YourPoolId")
                AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: configuration, forKey: "UserPool")
                pool = AWSCognitoIdentityUserPool.init(forKey: "UserPool")
                pool.delegate = self
        }

        @IBAction func logInButtonPressed(sender: UIButton) {
            pool.getUser().getDetails()
        }

        func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
            return self
        }


        func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
            self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
            let result = AWSCognitoIdentityPasswordAuthenticationDetails.init(username: "username", password: "password")
        }

}
Boris
  • 11,373
  • 2
  • 33
  • 35
  • You shoud change pool.getUser().getDetails() to pool.getUser().getSession("username", password: "password", validationData: nil). If your getDetails function fails in newPasswordRequired error, application will go into infinite loop. "getDetails()" function will executed repeatedly until get valid session. – Karim Karimov Aug 14 '18 at 05:42