BruceBuckland / SignIn-awsmhh

User Pools and Federated Identities sample. A Swift Sample Sign-In App (using aws-mobile hub-helper) with Cognito User Pools, Google and Facebook.
41 stars 6 forks source link

Can't integrate this work to own project #4

Closed ghost closed 7 years ago

ghost commented 7 years ago

After spending some time studying this project, I want to integrate it in my own project. Hélas, without success.

What I have done

Replaced AWSMobileHubHelper.framework and ViewController.swift obtained at AWS Mobile console by SignIn-awsmhh's.

Problem

App crashed after few seconds with following error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSDictionaryM removeObjectForKey:]: key cannot be nil'

This seems to be related to Identity Pool Configuration as stated by the log:

AWSiOSSDK v2.4.11 [Debug] AWSURLSessionManager.m line:553 | -[AWSURLSessionManager printHTTPHeadersForResponse:] | Response headers:
{
    Connection = "keep-alive";
    "Content-Length" = 143;
    "Content-Type" = "application/x-amz-json-1.1";
    Date = "Mon, 31 Oct 2016 17:28:24 GMT";
    "x-amzn-ErrorMessage" = "Invalid identity pool configuration. Check assigned IAM roles for this pool.";
    "x-amzn-ErrorType" = "InvalidIdentityPoolConfigurationException:";
    "x-amzn-RequestId" = "6d551129-9f8f-11e6-93ad-1f2317c04f0e";
}

AWSKeys.xcconfig is the same as in the SignIn-awsmhh project.

BruceBuckland commented 7 years ago

@Domsware sorry you are having trouble.

I don't know whether your problem is configuration or leaving out a step, but I did a step by step re-production of what you are trying to do and here are the steps. I tried them twice! They work.

(There might be easier processes than the one above to do all this, but this way you can use the symbolic debugger on the AWS code (which I find is necessary from time to time.))

git init
Add all the files to it (.gitignore file will make it omit your AWSKeys.xcconfig)
git add -A
git submodule add https://github.com/BruceBuckland/aws-mobilehub-helper-ios.git
cd aws-mobilehub-helper-ios

Now you have to get to the old Swift 2 version of the AWSCUPIdPSignInProvider.swift file. Because the swift3 version is not compatible with the Mobile Hub.

git checkout 79b396ca7c4718bac263d6c1604de96d15b67bf3 You can also use any commit before swift3 compatibility. This is only because the Mobile Hub downloads swift 2 code still

pod install (gets the requirements for the aws-mobile-hub-helper-ios)

./Scripts/GenerateHelperFramework.sh
cd ..
pod init
platform :ios, '9.0'

target 'MySampleApp' do
  # Comment this line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MySampleApp
      pod 'AWSCognito'
      pod 'AWSCognitoIdentityProvider'
end

pod install

    // CUPIdP changes

    // Now facebook and Google prompt for UID password, but in the demo app this signin view
    // prompts BEFORE the user even chooses the signInProvider.
    // For a "minimal change" fix, just to get User Pools working for the first time
    // I am going to stick the username and password into the shared
    // instance of AWSCUPIdPSignInProvider.  
    // If AWS had thought harder about AWSIdentityManager it would have a loginWithUsernamePassword 
    // method.

    func handleCustomLogin() {

        // Handle Login logic for User Pools Sign In using OpenID Connect
        //

        // let AWSInfoIdentityManager = "IdentityManager"

        if (customUserIdField.text != nil) && (customPasswordField.text != nil) {

            let customSignInProvider = AWSCUPIdPSignInProvider.sharedInstance

            // Push userId and password to our AWSCUPIdPSignInProvider

            customSignInProvider.customUserIdField = customUserIdField.text
            customSignInProvider.customPasswordField = customPasswordField.text

            handleLoginWithSignInProvider(customSignInProvider)

        }
    }

I know this just lets you login (the signup etc won’t work. You can use SignIn-awsmhh to create accounts). If you want to create accounts, update attributes, etc in the sample there is a little work to do, but not too much. If you can’t figure out how, you will have to take a look at the SignIn-awsmhh example.

BruceBuckland commented 7 years ago

@Domsware and just to make sure, I did a quick integration of MySampleApp (the downloaded Mobile Hub app) to SignIn-awsmhh. If you pull the latest version of that project it has two source trees and two targets. One is the standard Demo app from the Mobile Hub. Both have been set up to work with Swift 3. It is also modified to do identity merging.

ghost commented 7 years ago

@BruceBuckland

My Own Project

On one hand I started from a brand new Xcode project. Then, using Carthage, I added all the AWS frameworks needed. Do some tests and everything was fine.

SignIn-awsmhh

On the other hand I cloned SignIn-awsmhh to understand how things worked under the scene. I used a separated project to obtain AWSMobileHubHelper.framework then included it to SignIn-awsmhh. This project worked too and I was able to sign up users.

So, from this point I know:

Integrate SignIn-awsmhh to My Own Project

Then I wanted to add sign-in feature from SignIn-awsmhh project to my own project. So I followed all the procedure from the readme, mainly those following 3 important steps:

  1. AWSKeys.xcconfig
  2. aws-mobile-hub-helper-ios
  3. AWSCUPIdPSignInProvider

As stated before, first 2 points are OK. So the problem may come from 3.: AWSCUPIdPSignInProvider

I going to focus today on that point.

ghost commented 7 years ago

@BruceBuckland

I think we have to achieve the following goal: make it work from scratch. So we have to start from a fresh Xcode project and not from Mobile Hub Sample Project.

ghost commented 7 years ago

@BruceBuckland I don't understand the point about Swift 3 as AWS Mobile Hub officially does not support Swift 3!

BruceBuckland commented 7 years ago

@Domsware

What error do you get when you try to start from scratch?

Please post some code (and please post enough of the code so that I can see where you are) and the error you are getting.

That said: Lets take it from the "Minimalist" point of view.

The Mobile Hub download is pretty minimal (except that they automatically generate a demo for each capability demonstrated).

Minimally you would have to:

  1. Call

    • AWSMobileClient.sharedInstance.didFinishLaunching(application, withOptions: launchOptions)
    • AWSMobileClient.sharedInstance.withApplication(application, withURL: url, withSourceApplication: sourceApplication, withAnnotation: annotation as AnyObject)
    • AWSMobileClient.sharedInstance.applicationDidBecomeActive(application)

    at the appropriate AppDelegate moments (or do all the same code that AWSMobileClient does directly in AppDelegate.

    • Create a "Main" view controller that registers for the 3 NSNotifications (did sign in, did sign out and AWSMobileClient.AWSMobileClientDidCompleteInitialization (which is sent when the identityId is available upon startup)
    • do work from scratch in your Main view controller. You can depend upon having a set of credentials as soon as you get AWSMobileClient.AWSMobileClientDidCompleteInitialization.

Now probably, for most apps, you will also want to sign in. This would be to get "Authenticated" credentials (although you can do work with AWS Services using just the above steps. So usually you would have a "Sign In" button, and it would go to a "Sign In" view controller.

In your sign in view controller you would minimally call: AWSIdentityManager.defaultIdentityManager().loginWithSignInProvider (and pass one of the singletons from this list:)

  1. let singleton = AWSFacebookSignInProvider.sharedInstance().setPermissions(["public_profile"])
  2. let singleton = AWSGoogleSignInProvider.sharedInstance().setScopes(["profile", "openid"])
  3. let singleton = AWSCUPIdPSignInProvider.sharedInstance

If you use number 3, you need to set the Username and Password for the sign in. Numbers 1 and 2 get the usename and password in a webview or in safari during login.

The result is an AWSTask (Which task will get called passing "task" when the login process is complete. If task.error == nil you pop the view controller and you are logged in.)

Now you are back in the "Main" view controller where you can assume you are logged in as soon as you get the NSNotification AWSIdentityManagerDidSignInNotification.

That's it. Does that help?

ghost commented 7 years ago

Ok.

Start new project from Xcode (8)

Test it. OK

AWS Mobile Hub

User Sign-in

DynamoDB

Integrate

I select Option 2: Integrate these features into your mobile app

1. Download SDK and custom source code

2. Add the AWS SDKs as Embed Frameworks

Warnings: 0 Errors: 0 Run : OK Tests: OK

3. Add Run Script phase to your project

Name: AWS Mobile Hub

Warnings: 0 Errors: 0 Run : OK Tests: OK

4. Copy the Mobile Hub Helper and Custom Code

Warnings: 0 Errors: 0 Run : OK Tests: OK

5. Info.plist Configuration

Warnings: 0 Errors: 0 Run : OK Tests: OK

6. Add AWS iOS SDK Dependencies

Warnings: 0 Errors: 0 Run : OK Tests: OK

7. Setup Application Delegate

Warnings: 1 AppDelegate.swift:22:10: Instance method 'application(application:openURL:sourceApplication:annotation:)' nearly matches optional requirement 'application(_:open:sourceApplication:annotation:)' of protocol 'UIApplicationDelegate'

Errors: 3 AppDelegate.swift:19:16: Use of unresolved identifier 'AWSMobileClient'

BruceBuckland commented 7 years ago

@Domsware The reason I am doing Swift 3 is because i am trying to get comfortable with Swift 3.
But you are right, Swift 3 is not supported by the Mobile Hub. I will make a branch of aws-mobile-hub-helper-ios that is Swift 2 only (in my instructions I have you do a "get checkout" which basically spins back the clock to a time when my aws-mobile-hub-helper-ios was Swift 2.

I will make a branch that can be used for Swift 3.

ghost commented 7 years ago

@BruceBuckland

I too wanted to use Swift 3. Unfortunately this cannot currently be done. Even in Swift 2 basic things are hard to setup as 1/ most iOS SDK samples are done using Objective-C and 2/ the Swift examples are ,or at least seem to be, outdated.

BruceBuckland commented 7 years ago

Ok, one comment:

regarding your note:

I have never tested it with "sign in is required". I don't know if that would cause a problem.

ghost commented 7 years ago

Yes, you're right. I have to test it without it!

BruceBuckland commented 7 years ago

@Domsware From your comment I select Option 2: Integrate these features into your mobile app i can see that you are using the non-download approach in Mobile Hub.

I have never tried that approach. It says it cannot find AWSMobileClient. This is called from AppDelegate and is needed. I have also modified it in my code to add a new notification so that the main view controller can tell when the identityId is available.

ghost commented 7 years ago

Yes, AWSMobileClient is needed in AppDelegate. But, procedure does not mention how to include it!

BruceBuckland commented 7 years ago

Well right click on your source directory and click add files to....

Click options and choose the target click "Copy items if needed" and "create groups" then choose the correct version of AWSMobileClient and click add.

(The procedure you are following is an AWS procedure I have not tried, the one above i tried.)

ghost commented 7 years ago

@BruceBuckland

Yes this is the thing to do. Now it works. I have done the overall procedure using Xcode 7. As I want to use Xcode 8 for many reasons but mainly because Xcode 7 crash a lot on my computer.

So I open project on Xcode 8, follow some recommandations and set "Use Legacy Swift Language version" to YES on project > target > Build Settings

Everything is fine without any warning, errors and it runs.

So now I have a clean basis and I'm gonna follow all your procedure.

By the way, starting project using Xcode 8 does not work as Swift 3 is then used. Am I correct?

ghost commented 7 years ago

@BruceBuckland,

I have some questions about AWSKeys.xcconfig:

  1. what is COGNITO_USER_POOL_APP?
  2. why COGNITO_USER_POOL_IDP_NAME? As is can be deducted from COGNITO_REGIONTYPE and COGNITO_USER_POOL_ID?
BruceBuckland commented 7 years ago

@Domsware As long as you choose Use Legacy Swift Language version" set to YES (on the project and the target, because the target can override) then Xcode 8 compiles using Swift 2.3.

Glad you are "Out of the woods".

I have been having trouble with the Swift 3 conversion especially when I try to use the full demo and all it's features. I am getting crashes on:

I am going to make a tag stays unchanged with basic enhancements for aws-mobilehub-helper-ios and push that to github

For SignIn-awsmhh I am going to make the master branch Swift 2 compatible and then I will create separate changes on a master-Swift-3 branch.

So don't pull master for SignIn-awsmhh till I do that, because currently it is swift 3 and it will screw everything up for you.

aws-mobilehub-helper-ios master branch is 100% Obj C, so it is Swift3 and Swift2 compatible. I will post something here when I do it. ... Update: Ok I did it. There are now branches for swift 2 and swift 3, and the master is Swift 2 only.

BruceBuckland commented 7 years ago

@Domsware

  1. why COGNITO_USER_POOL_IDP_NAME? As is can be deducted from COGNITO_REGIONTYPE and COGNITO_USER_POOL_ID?
    • This one is more complicated than it seems. You can't really deduce them without substantial processing because region types are specified in at least 3 different ways by AWS (depending upon language and documentation). So if the user specified any legal region type I wanted it to work.
    • This is so much of a problem that AWS fixed it by creating an extension to the String class that adds a method called: .aws_regionTypeValue(). This is a neat little function that takes all the different kinds of ways that AWS specifies regions (USEast1, US-East-1, us-east-1), and turns them into a region value. "any-way-aws-ever-used-to-specify-a-region".aws_regionTypeValue() always works.
    • COGNITO_USER_POOL_IDP_NAME is a string which will be returned in response to the identityProviderName call to the AWSCUPIdPSignInProvider. It is used to construct the logins dictionary (you can see this in the log) and must match exactly what the OpenID Connect provider for the Cognito User Pool produces or the credentialsProvider will not recognize the login as authentic.
    • Unfortunately AWS did not produce an inverse function to make a known string format out of an actual Region Type specifier ( and I certainly wasn't going to try to do it for them, they have 3 different formats already for no good reason, who knows what they will do next).
  2. what is COGNITO_USER_POOL_APP?
    • This is a vestige of an earlier time. It is no longer needed and may be removed from AWSKeys.xcconfig. Initially I had thought I would make the provider key, that is used to store in NSUserDefaults, be a simple name related to the application. (That way I thought I could maintain sessions on a per-application basis - but since google and facebook don't I changed my mind). So later I decided to make the provider key be a human readable name (like it is for Google and Facebook) but I never removed the old key.

The human readable name (provided in AWS->IdentityManager->Default->SignInProviderKeyDictionary) helps when it is time to display error messages to the user because "Google Sign In Error" sounds better than "cognito-idp.us-east-1.amazonaws.com/us-east-1_k999uG9999 Sign In Error".

ghost commented 7 years ago

@BruceBuckland

A little step in the right direction! But now I don't understand all the explanations about the replacement of the current aws-mobile-hub-helper-framework, the one provide by AWS, by your version.

What I have done before is to clone aws-mobile-hub-helper-framework from GitHub, generate the framework and use this one to replace the AWS' version in my own project. During the long explanations you brought on the beginning of this issue, there is a long part about getting aws-mobile-hub-helper-framework built. But on SignIn-awsmhh's readme there is only a small part. So I am confused!

And, finally, what about AWSCUPIdPSignInProvider. Where to find it? Remember I start from a blank project and everything have to be provided as it is not as in your SignIn-awsmhh.

BruceBuckland commented 7 years ago

Part of the issue here is that I am struggling to learn git, so apologies.

AWSCUPIdPSignInProvider.swift is now distributed only as part of SignIn-awsmhh repository. You must use the correct branch of that. That is master or you can use the branch called master-Swift-2

git clone --recursive https://github.com/BruceBuckland/SignIn-awsmhh.git

As far as the aws-mobilehub-helper-ios repository, you need to pull from my fork off of the master in order to get the capabilities you need to support AWSCUPIdPSignInProvider.swift to work. If you want to clone that repository separately you can, but it is not necessary because the git clone --recursive above gets this repository as a submodule of SignIn-awsmhh.

git clone https://github.com/BruceBuckland/aws-mobilehub-helper-ios.git

ghost commented 7 years ago

@BruceBuckland

I am a newbie in Git too!

Sorry but I don't understand your last comment and what to do. What I understand is to 1/ build aws-mobilehub-helper-ios and 2/ copy/pasteAWSCUPIdPSignInProvider.swift from your projet to mine.

I build aws-mobilehub-helper-ios then replace in my own project.

Then clone the master-Swift-2 branch for SignIn-awsmhh to get AWSCUPIdPSignInProvider.swift. But there are a lot of errors. :-(

First, AWSCognitoIdentityProvider is not found. Then there are a lot of errors due to fileprivate instruction. So I wonder if these repository if for Swift2...

ghost commented 7 years ago

OK, one problem is from my side. It appears my clone of SignIn-awsmhh was not the master-Swift-2 branchas I was expected. Didn't I tell you I was a newbie in GitHub!

And the second one is solved: AWSCognitoIdentityProvider framework was missing. So adding it, and changing some code to correct errors due to my newbieness in GitHub and now project build and run.

I still don't achieve my goal due to some delegate stuff missing but I'm on it.

ghost commented 7 years ago

Now app build and run.

An error occured:

I have inspected you code and did not found where a delegate is set.

BruceBuckland commented 7 years ago

@Domsware ...

Regarding compile errors.

First, AWSCognitoIdentityProvider is not found.

Then there are a lot of errors due to fileprivate instruction. So I wonder if these repository if for Swift2...

(Please show errors)

I am happy to help if I can, we are both learning from this. May I suggest that you shoot me a copy of 3 files. Your Appdelegate, MainViewController and SignInviewcontroller.

Your english is excellent, but I am worried that we are not really communicating well, and if I saw what you were doing in the code, I think I could help.

ghost commented 7 years ago

Hello, thank you for your kind words!

Swift 3 case

Solved. I cloned project from GitHub without changer branch...

AWSCognitoIdentityProvider is not found

I've solved problems with AWSCognitoIdentityProvider is not found. I use Carthage to build framework then include it in the project.

capture d ecran 2016-11-03 a 14 52 28

I'm not sure what to use to add framework: Embedded Binaries or Linked Frameworks and Libraries

AppDelegate

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        return AWSMobileClient.sharedInstance.didFinishLaunching(application, withOptions: launchOptions)
    }

    func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
        return AWSMobileClient.sharedInstance.withApplication(application, withURL: url, withSourceApplication: sourceApplication, withAnnotation: annotation)
    }

    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

        AWSMobileClient.sharedInstance.applicationDidBecomeActive(application)
    }

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }

}

SignInViewController

There is no MainViewController as this is a prototype: my goal is to sign-in and create some stuff with Dynamo and Lambda.

//
//  ViewController.swift
//

import UIKit

import AWSCore
import AWSCognitoIdentityProvider
import AWSMobileHubHelper

class ViewController: UIViewController, AWSCognitoIdentityPasswordAuthentication {

    var usernameText: String?

    // loggedIn state change observers
    var didSignInObserver: AnyObject!
    var didSignOutObserver: AnyObject!
    var didCompleteInitializationObserver: AnyObject!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        didSignInObserver = NSNotificationCenter.defaultCenter().addObserverForName(
            AWSIdentityManagerDidSignInNotification,
            object: AWSIdentityManager.defaultIdentityManager(),
            queue: NSOperationQueue.mainQueue(),
            usingBlock: {(note: NSNotification) -> Void in

                // perform successful login actions here
                if AWSIdentityManager.defaultIdentityManager().currentSignInProvider is AWSCUPIdPSignInProvider {
                    // only remember the name of the user if it is a CUPIdP name
                    self.usernameText = AWSIdentityManager.defaultIdentityManager().userName
                    print(">>>>> Sign In Observer observed sign in for: " + self.usernameText!)
                }
        })

        didSignOutObserver = NSNotificationCenter.defaultCenter().addObserverForName(AWSIdentityManagerDidSignOutNotification, object: AWSIdentityManager.defaultIdentityManager(), queue: NSOperationQueue.mainQueue(), usingBlock: {[weak self](note: NSNotification) -> Void in
            print(">>>>> Sign Out Observer observed sign out.")
            })

        // when we really have an identityId - start processing.
        didCompleteInitializationObserver = NSNotificationCenter.defaultCenter().addObserverForName(AWSMobileClient.AWSMobileClientDidCompleteInitialization, object: AWSMobileClient.sharedInstance, queue: NSOperationQueue.mainQueue(), usingBlock: {[weak self](note: NSNotification) -> Void in

            print(">>>>> Initialization of AWSIdentityManager complete, we now have an identityId")
            })

    }

    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(didSignInObserver)
        NSNotificationCenter.defaultCenter().removeObserver(didSignOutObserver)
        NSNotificationCenter.defaultCenter().removeObserver(didCompleteInitializationObserver)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func actionSignIn(sender: AnyObject) {

        let identifiant:String? = "Dominum"
        let motDePasse:String?  = "ThePasSWoRD"

        if (identifiant != nil) && (motDePasse != nil) {

            let customSignInProvider = AWSCUPIdPSignInProvider.sharedInstance

            // Push userId and password to our AWSCUPIdPSignInProvider

            customSignInProvider.customUserIdField = identifiant
            customSignInProvider.customPasswordField = motDePasse

            handleLoginWithSignInProvider(customSignInProvider)

        }

    }
    func showAlert(titleText: String, message: String) {
        var alertController: UIAlertController!
        alertController = UIAlertController(title: titleText, message: message, preferredStyle: .Alert)
        let doneAction = UIAlertAction(title: NSLocalizedString("Done", comment: "Label to cancel dialog box."), style: .Cancel, handler: nil)
        alertController.addAction(doneAction)
        presentViewController(alertController, animated: true, completion: nil)
    }

    func showErrorDialog(loginProviderName: String, withError error: NSError) {
        print("\(loginProviderName) failed to sign in w/ error: \(error)")
        if let message = error.userInfo["message"] {
            showAlert(NSLocalizedString("\(loginProviderName) Sign-in Error", comment: "Sign-in error for sign-in failure."), message: NSLocalizedString("Sign in using \(loginProviderName) failed: \(message)", comment: "Sign-in message structure for sign-in failure."))
        } else if let message = error.userInfo["NSLocalizedDescription"]{
            showAlert(NSLocalizedString("\(loginProviderName) Sign-in Error", comment: "Sign-in error for sign-in failure."), message: NSLocalizedString("Sign in using \(loginProviderName) failed: \(message)", comment: "Sign-in message structure for sign-in failure."))
        } else {
            showAlert(NSLocalizedString("\(loginProviderName) Sign-In Error", comment: "Sign-in error for sign-in failure."), message: NSLocalizedString("\(loginProviderName) failed to sign in w/ error: \(error)", comment: "Sign-in message structure for sign-in failure."))
        }
    }

    func handleLoginWithSignInProvider(signInProvider: AWSSignInProvider) {

        AWSIdentityManager.defaultIdentityManager().loginWithSignInProvider(signInProvider, completionHandler: {(result: AnyObject?, error: NSError?) -> Void in
            // If no error reported by SignInProvider, discard the sign-in view controller.
            if error == nil {
                dispatch_async(dispatch_get_main_queue(),{
                    self.navigationController!.popViewControllerAnimated(true)
                })
            } else {
                dispatch_async(dispatch_get_main_queue(),{
                    self.showErrorDialog(AWSIdentityManager.defaultIdentityManager().providerKey(signInProvider), withError: error!)
                })
            }
            print("result = \(result), error = \(error)")

        })
    }

    // MARK: Protocole AWSCognitoIdentityPasswordAuthentication
    func getPasswordAuthenticationDetails(authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource) {

    }

    func didCompletePasswordAuthenticationStepWithError(error: NSError?) {

    }

}
BruceBuckland commented 7 years ago

@Domsware There are 2 approaches to connecting the libraries to your code.

Maybe you can help me make this better.

Do you know how to make the script that builds aws-mobilehub-helper-ios put the modules in the framework so that I can use the debugger? That would be MUCH simpler. But I could not make it work. It works for the AWS Cocoapods. But it doesn't work for MobileHubHelper.framework (which is a static library).

You say you use Carthage. I have never tried that. Does that make the debugging problem easier?

The two approaches

Which one you use depends on how much you want to be able to use "lldb" (the debugger) in the AWS code.

For me, I MUST run the debugger in AWS code because I keep finding bugs, and it is so poorly documented that I need to use the debugger to figure out problems. So I do #2, but #1 is simpler.

2 options (choose 1)

1) Build the library and drag the .framework file into frameworks.

2) Create the whole project as a submodule in git of my project. This gives me full access to the debugger. But I have to do two extra things:

I create a "git submodule" for the repository. This is done by going to your project directory and:

git submodule add https://github.com/BruceBuckland/aws-mobilehub-helper-ios.git

That creates a submodule link in your git repository.

Then right click your project and "Add files...." and I choose the "MobileHubHelper" directory, and choose options, and UNCLICK the "Copy Items as Needed".

Then whenever the aws-mobilehub-helper-ios changes I just do a

git submodule update
BruceBuckland commented 7 years ago

@Domsware is Carthage what I should do? I have never used it.

As far as how to include the MobileHubHelper.framework, it works just to drag it to your Framework directory (approach #1 above)

ghost commented 7 years ago

Here are the observers' log

  1. Sign Out Observer observed sign out.
  2. Initialization of AWSIdentityManager complete, we now have an identityId
  3. Sign In Observer observed sign in for: MeMyselfAndI
  4. Sign Out Observer observed sign out.
ghost commented 7 years ago

Do you know how to make the script that builds aws-mobilehub-helper-ios put the modules in the framework so that I can use the debugger?

Unfortunately, no

You say you use Carthage. I have never tried that. Does that make the debugging problem easier?

I don't know. I use Carthage because it is less intrusive than CocoaPods. Sources are retrieved then build and that's all.

BruceBuckland commented 7 years ago

Thanks for your code. My comments are as follows; @Domsware

1) On line 118, when you POP the view controller ... it would normally go to a screen where you are logged in (the Main View Controller) but you don't have one. So I don't know what would happen.

2) From your log, it looks like you are getting signed in. - and all should be well.

3) We are using user pool signin with username and password, so making the view controller comply with AWSCognitoIdentityPasswordAuthentication is not needed nor are the two methods getPasswordAuthenticationDetails and didCompletePasswordAuthenticationStepWithError. Those methods are needed when you are using the AWSCognitoIdentityInteractiveAuthenticationDelegate. But this code doesn't use that (currently). With that other approach you set a delegate that gets called when a user tries to get a session or get user details while "not" authenticated. In that case... the delegate gets called to collect a username and password, and the protocol provides a completion callback routine. It doesn't hurt that you have that code there, but it is not needed (yet).

ghost commented 7 years ago

@BruceBuckland

I put a breakpoint that pause execution on that line to warn me if everything is fine. I'll change the overall design once connection is OK.

Yes, I am sign in but only for few microseconds! Then there is a sign out. But there is something that worry me a lot. Once Sign-In I get an identityId but these IdentityId change after each run. That is not the expected behavior. According to documentation the IdentityId must be unique for the same user. Am I correct?

ghost commented 7 years ago

Here is the alert presented after sign-in:

capture d ecran 2016-11-03 a 11 47 19
ghost commented 7 years ago

And here is the app/ very simple!

capture d ecran 2016-11-03 a 15 46 03
BruceBuckland commented 7 years ago

"Authentication delegate not set" is true, ,because we are not using an authentication delegate.

So remove the AWSCognitoIdentityPasswordAuthentication protocol. And the two unused methods as mentioned in an earlier post.

If you want to actually use authentication delegates

If you want to actually SET an Authentication Delegate you would do that on the pool. You can get access to the pool from the sign in provider (not from facebook or google but just from CUPIdP).

// this will get you the shared instance
let provider =AWSIdentityManager.defaultIdentityManager().currentSignInProvider
// this will set the user pool delegate
provider.pool.delegate = (your delegate, probably "self" )
BruceBuckland commented 7 years ago

Regarding identity id's, please take a look at the cognito-diagram.pdf file included in the SignIn-awsmhh project. On page 7 and 8 it describes the behavior of identityId's.

What you are seeing is "normal" behavior. As those pages explain the expected use case is the user logs in and never logs out. If he logs out, and unauthenticated identities are allowed, then it goes and gets one (the old one is unknown at that point). If he never logs in, and just uses the unauth identity, then it stays the same. The identityId is stored in a keychain, and authenticating overwrites it.

if he stays logged in it stays the same too.

ghost commented 7 years ago

So I removed AWSCognitoIdentityPasswordAuthentication protocol and related stuff, then clean a bit — derivedData, Clean Buil Folder — but the problem still here.

I don't understand the point about identityId. Logs show identityId take 2 different values during the app execution: the second one occurs after touching the Sign-In button. In my understanding the first one is unauth identityId and the second one must be the auth identityId. But in this case I am devastated because it changes all the time and that is the primary key for my Dynamo Tables...

BruceBuckland commented 7 years ago

Using the console check to see if you have a logins count on the identityId (browse identities) If you don't you are not getting logged in.

The identityId should be the same each time you log in with the same username. But perhaps you are never getting signed in fully because of the Authentication delegate not set error. Is that error still happening?

If you log in, the identity id is stored in a keychain. The next time you connect, you will still be logged in. In your current app I am not sure you are ever getting fully signed in.

IdentityId Behaviors

In practice the common use case would not create this barrage of disabled identityIds. A user would:

  1. Connect – get an unauthenticated id - authenticate – and use the same ID. No abandoned id is created.
  2. Connect on another device – here he/she would momentarily get a new unauthenticated id – and when he/she authenticated and got the identityId for his/her identity, that unauthenticated id would be disabled and abandoned.
  3. Each merging of identities from two identity providers would also create a disabled and abandoned identityId.
ghost commented 7 years ago

Ok, I'll give a look at this.

I am new to AWS but this is hell! I am currently exchanging with support about creating users from admin, Console or CLI, that are fully usable. I can't succeed to achieve this without external tricks. So, after days of describing the issue, describing it again because I was sent on wrong trails, I then have 2 answers: the first says it is not possible to achieve the whole process using Console or CLI — that is the situation I am in, and the second says that it is possible!

So AWS seems to be a big hell, a big mess!

ghost commented 7 years ago

I've read your great PDF days ago and it helped me to understand the process. Is the solution to use IdentityId to link data on DynamoDB to Cognito Users is OK? After reading your PDF and asking to AWS support I thought it was. But now as the simple fact to identify a user is a nightmare and AWS stuff is in a poor state — documentation (uncompleted/outdated)+Samples (uncompleted/outdated)+SKD(undocumented)

ghost commented 7 years ago

So I gave another try:

So I launched the app at 18h11, local time, UTC+1

18h11 — Run App

IdentityId: eu-west-1:12345678-.... Cognito Console, dateCreated: 2016-11-03T17:11:05Z UTC

Date are the same so this IdentityId has been created by this event.

Requests:

18h15 — Sign-In

IdentityId: eu-west-1:ABCDEFGHI... Cognito Console, dateCreated: 2016-11-03T17:15:11Z

This is a new IdentityId. Date are the same so this IdentityId has been created by this event.

Requests:

Conclusion

A new IdentityId is created after signIn. And as it is new it cannot be uses as a database PrimaryKey...

BruceBuckland commented 7 years ago

@Domsware

i compiled an app with your app delegate and viewcontroller. It is called "Dom" and is in SignIn-awsmhh. (If you want I can push it as part of the project, right now I have it in .gitignore

Changes:

I needed to add a number to the password (my policy required it, does yours?) I signed up the user first with SignIn by pressing SignUp (and I confirmed it) to get a verified email user account in the user pool.

        let identifiant:String? = "Dominum"
        let motDePasse:String?  = "TTTppp999"

Observations (I think this works the way you want)

"Logins don't match. Please include at least one valid login for this identity or identity pool."

Then Cognito (credentials provider) does a form of "retry", it will do a GetId, gets the correct ID and then does a getCredentialsForIdentity which will find the new logins dictionary (because the token returned by the AWSCUPIdPSignInProvider will be different now that the user logged in is different). Then that identity will be used.

You could avoid all that by logging out before logging in. But when you log out if you try to do anything that needs credentials you will get a new unauthenticated id.

Minor changes to your View Controller to let me see what is going on. Starting around line 44

        // when we really have an identityId - start processing.
        didCompleteInitializationObserver = NSNotificationCenter.defaultCenter().addObserverForName(AWSMobileClient.AWSMobileClientDidCompleteInitialization, object: AWSMobileClient.sharedInstance, queue: NSOperationQueue.mainQueue(), usingBlock: {[weak self](note: NSNotification) -> Void in

            print(">>>>> Initialization of AWSIdentityManager complete, we now have an identityId \(AWSIdentityManager.defaultIdentityManager().identityId)")
            })

    }

    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(didSignInObserver)
        NSNotificationCenter.defaultCenter().removeObserver(didSignOutObserver)
        NSNotificationCenter.defaultCenter().removeObserver(didCompleteInitializationObserver)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func actionSignIn(sender: AnyObject) {

        let identifiant:String? = "Dominum"
        let motDePasse:String?  = "TTTppp999"

        if (identifiant != nil) && (motDePasse != nil) {

            // BEFORE you log in you may have sessions left over
            let identityProvidersWithActiveSessions = AWSIdentityManager.defaultIdentityManager().activeProviders()
            NSLog("Active Sessions Exist with these signin providers: \(identityProvidersWithActiveSessions)")

            let customSignInProvider = AWSCUPIdPSignInProvider.sharedInstance

            // Push userId and password to our AWSCUPIdPSignInProvider

            customSignInProvider.customUserIdField = identifiant
            customSignInProvider.customPasswordField = motDePasse

            handleLoginWithSignInProvider(customSignInProvider)

        }

    }
    func showAlert(titleText: String, message: String) {
        var alertController: UIAlertController!
        alertController = UIAlertController(title: titleText, message: message, preferredStyle: .Alert)
        let doneAction = UIAlertAction(title: NSLocalizedString("Done", comment: "Label to cancel dialog box."), style: .Cancel, handler: nil)
        alertController.addAction(doneAction)
        presentViewController(alertController, animated: true, completion: nil)
    }

    func showErrorDialog(loginProviderName: String, withError error: NSError) {
        print("\(loginProviderName) failed to sign in w/ error: \(error)")
        if let message = error.userInfo["message"] {
            showAlert(NSLocalizedString("\(loginProviderName) Sign-in Error", comment: "Sign-in error for sign-in failure."), message: NSLocalizedString("Sign in using \(loginProviderName) failed: \(message)", comment: "Sign-in message structure for sign-in failure."))
        } else if let message = error.userInfo["NSLocalizedDescription"]{
            showAlert(NSLocalizedString("\(loginProviderName) Sign-in Error", comment: "Sign-in error for sign-in failure."), message: NSLocalizedString("Sign in using \(loginProviderName) failed: \(message)", comment: "Sign-in message structure for sign-in failure."))
        } else {
            showAlert(NSLocalizedString("\(loginProviderName) Sign-In Error", comment: "Sign-in error for sign-in failure."), message: NSLocalizedString("\(loginProviderName) failed to sign in w/ error: \(error)", comment: "Sign-in message structure for sign-in failure."))
        }
    }

    func handleLoginWithSignInProvider(signInProvider: AWSSignInProvider) {

        AWSIdentityManager.defaultIdentityManager().loginWithSignInProvider(signInProvider, completionHandler: {(result: AnyObject?, error: NSError?) -> Void in
            // If no error reported by SignInProvider, discard the sign-in view controller.
            if error == nil {
//                dispatch_async(dispatch_get_main_queue(),{
//                    self.navigationController!.popViewControllerAnimated(true)
//                })
            } else {
                dispatch_async(dispatch_get_main_queue(),{
                    self.showErrorDialog(AWSIdentityManager.defaultIdentityManager().providerKey(signInProvider), withError: error!)
                })
            }
            print("result = \(result), error = \(error)")

        })
    }
BruceBuckland commented 7 years ago

@Domsware

to your recent comments:

1) Your conclusion is incorrect. (You must look at ALL of the identityId's that end up in the log, as they change from time to time. Once logged in the identityId for the Dominum user will never change. Take a look at my response. Using identityId as a primary database key should work just fine. See my previous post. 2) Yes, AWS is not doing good work here... but the system (especially the mobile hub helper library) is quite good, and manages identity very well. t is just incredibly poorly documented. That is why I took a lot of notes, and produce the summary document you saw. (I have an even larger more complete version of that presentation but it is not in final form, because of that I would send it to you in Email if you want, but I don't want to post it.) 3) AWS CLI is useful, I have a set of scripts on GitHub (in a different repository in my area) that use Ruby to drive the AWS CLI and list and clean up user pools and identity pools. This is useful for testing.

ghost commented 7 years ago

@BruceBuckland

Thank you for all these informations. We choose AWS because we have identified it to be a very strong service for our need. But the learning path for such complex services is long and hard and that's difficult to explain to our managers!

Ok, It's late here and I have to stop working for today. So have a nice night.

ghost commented 7 years ago

Ok, I've done the homework!

On simulator, Xcode 8, I run the app over and over and DON'T press signin (ON THE SAME DEVICE (the same simulator))

The IdentityId is different each time. 👎

Here is some logs:

didFinishLaunching:
2016-11-04 11:25:17.070 DV-test-cognito_swift2[2725:49789] OSStatus error: [-34018] Security error has occurred.
2016-11-04 11:25:17.072 DV-test-cognito_swift2[2725:49789] OSStatus error: [-34018] Security error has occurred.
2016-11-04 11:25:17.073 DV-test-cognito_swift2[2725:49789] OSStatus error: [-34018] Security error has occurred.
2016-11-04 11:25:17.074 DV-test-cognito_swift2[2725:49789] OSStatus error: [-34018] Security error has occurred.
2016-11-04 11:25:17.085 DV-test-cognito_swift2[2725:49833] AWSiOSSDK v2.4.11 [Debug] AWSURLSessionManager.m line:526 | -[AWSURLSessionManager printHTTPHeadersAndBodyForRequest:] | Request headers:
{
    "Content-Type" = "application/x-amz-json-1.1";
    Host = "cognito-identity.eu-west-1.amazonaws.com";
    "User-Agent" = "aws-sdk-iOS/2.4.11 iOS/10.1 en_US";
    "X-Amz-Date" = 20161104T102517Z;
    "X-Amz-Target" = "AWSCognitoIdentityService.GetId";
}
2016-11-04 11:25:17.085 DV-test-cognito_swift2[2725:49833] AWSiOSSDK v2.4.11 [Debug] AWSURLSessionManager.m line:543 | -[AWSURLSessionManager printHTTPHeadersAndBodyForRequest:] | Request body:
{"IdentityPoolId":"eu-west-1:73e5d1d1-011a-4860-bc8e-13fc840a2de2"}
applicationDidBecomeActive:
2016-11-04 11:25:17.338 DV-test-cognito_swift2[2725:49833] AWSiOSSDK v2.4.11 [Debug] AWSURLSessionManager.m line:553 | -[AWSURLSessionManager printHTTPHeadersForResponse:] | Response headers:
{
    Connection = "keep-alive";
    "Content-Length" = 63;
    "Content-Type" = "application/x-amz-json-1.1";
    Date = "Fri, 04 Nov 2016 10:25:17 GMT";
    "x-amzn-RequestId" = "fbb272b9-a278-11e6-b22b-07ce008c8dbb";
}
2016-11-04 11:25:17.338 DV-test-cognito_swift2[2725:49833] AWSiOSSDK v2.4.11 [Debug] AWSURLResponseSerialization.m line:63 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body:
{"IdentityId":"eu-west-1:2283c372-47ea-492d-aab7-8382a5188597"}
2016-11-04 11:25:17.341 DV-test-cognito_swift2[2725:49848] OSStatus error: [-34018] Security error has occurred. 

About request

Before press signin, there is 2 requests:

After press signing there are 3 more requests:

And after the last request the sign out observer is fired:

Sign Out Observer observed sign out.

Others trails

OSStatus error: [-34018] Security error has occurred.

There a lot of these errors on log. Don't know where it come from but that may be related to app sign-in as pointed by Xcode.

App settings on Cognito User Pools

Does these settings may cause these kind of problems?

ghost commented 7 years ago

Have you any idea about the "delegate" case: why there is an error about delegate as there is not need for it?

ghost commented 7 years ago

@BruceBuckland

So, in order to understand what is going on, I did some test on a fresh identity pool:

Then notification about the delegate appeared.

ghost commented 7 years ago

Good new: I succeed to establish a connection! But this is using a device and not simulator...

BruceBuckland commented 7 years ago

When you talk about clearing out pools to start from scratch - that can be helpful. But you need to clear the simulator too! Each device in the simulator is a separate iPhone and so you either need to clear it or switch to a clear device.

So: In the simulator do "Simulator -> Reset Content and Settings..."

The reason for this is because of where Cognito Buries its Data.

Cognito keeps:

Both of these can cause you problems in the simulator while you are debugging. Get used to "Simulator -> Reset Content and Settings..." until your code is stable.

BruceBuckland commented 7 years ago

As to delegate question:

Am I correct that your code has

class ViewController: UIViewController, AWSCognitoIdentityPasswordAuthentication {

And also has methods for getPasswordAuthenticationDetails and didCompletePasswordAuthenticationStepWithError

If so, I suspect that some part of the code is doing "key value" checks and saying ... if he responds to such and such a method, then make sure he has a delegate. The delegate is not set, it does not need to be. I can tell you how to set it but then you would have to code to handle it.

Both of these things

So: Try deleting AWSCognitoIdentityPasswordAuthentication didCompletePasswordAuthenticationStepWithError and getPasswordAuthenticationDetails

BruceBuckland commented 7 years ago

If you go into the pool is the "logins" count on the identityId greater than 0? If not, you are NEVER getting logged in and therefore never getting an ID.

OSStatus error: [-34018] Security error has occurred.

That error occurs because you need to set the "Keychain Sharing" capability to "ON" in your "Target->Capabilities" section.

But that is a good point i am going to add that to my Cognito Overview