Pools d'users AWS Cognito dans l'application iOS (Swift)

J'essaie d'implémenter les nouveaux pools d'users AWS Cognito dans mon application iOS (Swift), mais j'ai du mal à faire fonctionner le process de connection. Essentiellement, j'essaie de suivre l'exemple disponible ici .

C'est ce que j'ai jusqu'ici:

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 : Ssortingng? 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)) } } 

Rien ne semble se produire lorsque j'appuie sur le button de connection, bien que si je le clique de nouveau, j'obtiens une exception NSInternalInconsistencyException (ce qui est dû au fait que le résultat de AWSTask a déjà été défini).

Toute aide avec ceci serait appréciée. J'utilise le SDK AWS pour iOS version 2.4.1.

METTRE À JOUR:

Ce n'est pas une solution à mon problème d'origine, mais j'ai réussi à faire fonctionner les pools d'users en utilisant la méthode de connection explicite plutôt que la méthode déléguée (voir cette page pour plus de détails). Voici le code de mon 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 } } } 

Ensuite, pour consumr un service AWS (qui dans mon cas se trouve dans une autre région que Cognito), j'ai créé un nouveau fournisseur d'informations d'identification en utilisant le pool d'users:

 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") 

Un problème supplémentaire est que je voyais cette erreur chaque fois que je lançais l'application: "Impossible de find les valeurs 'AWSDefaultRegionType', 'AWSCognitoRegionType' et 'AWSCognitoIdentityPoolId' valides dans info.plist.". Cela semble être lié à Fabric, que j'utilise pour suivre les accidents. J'ai résolu ceci en changeant cette ligne dans le AppDelegate:

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

pour ça:

 Fabric.with([Crashlytics.self]) 

J'espère que ça aidera quelqu'un d'autre.

    Mise à jour 6: (et vraiment définitive cette fois)

    Il est à noter que (finalement) AWS a fait de l'AWS Mobile Hub une très belle application de démonstration qui inclut des pools d'users en tant que SignInProvider (avec Google et Facebook aussi). L'architecture est (à mon avis) excellente (ils ont séparé Identity Management et get des informations d'identification de l'authentification) Check it out

    Mise à jour 5: (et finale)

    Il y a un exemple de mise en œuvre assez complet, et une documentation sur la façon dont cela fonctionne dans cette autre réponse.

    iOS – AWS MobileHub se connecte avec un fournisseur authentifié par développeur

    Mise à jour 4:

    Si vous souhaitez accéder aux services AWS, d'autres étapes sont nécessaires

    Il s'avère que cela ne vous obtient pas authentifié avec Cognito Federated Identities (le nombre de "logins" sur le browser d'identité rest à 0). Pour résoudre ce problème, vous devez établir un credentialsProvider et faire "credentialsProvider.getIdentityId". Après cela, les connections s'afficheront de manière positive et vous pourrez get des services d'AWS en fonction de votre rôle authentifié.

    Si vous essayez d'accéder à la fois à l'authentification et à l'authentification non authentifiée pour votre application mobile, vous devez créer un AWSAnonymousCredentialsProvider (dans une configuration de service distincte). Ensuite, self.credentialsProvider? .invalidateCachedTemporaryCredentials () et self.credentialsProvider? .clearCredentials () lorsque vous vous déconnectez et refaites getidentityid avec la configuration du service anonyme et vous obtiendrez un identifiant anonyme. (Note: j'ai trouvé que si vous clearkeychain sur le credentialsProvider, il semblait que vous commenciez avec un nouvel identifiant à chaque fois qu'un user se déconnecte, ce qui pourrait rapidement brûler vos 50 000 ID gratuits.)

    Mise à jour 3:

    Envoyé un exemple d'application github pour les pools d'users AWS pour IOS dans Swift.

    https://github.com/BruceBuckland/signin

    Mise à jour 2:

    J'ai finalement obtenu AWS User Pools pour fonctionner correctement dans Swift

    Mon problème était que chaque fois que le démarrage de l'authentification se produisait, il était causé par un échec d'authentification dans un autre viewcontroller (mon erreur). Je me suis retrouvé avec un tas d'entre eux en cours d'exécution en attente d'achèvement des returns qui ne sont jamais venus et l'API était "silencieux" (n'a montré aucune erreur). L'API ne remarque pas qu'elle est initiée plusieurs fois (par un viewController différent à chaque fois), ce qui permet de se connecter en boucle à plusieurs resockets. Il n'y a pas assez de code dans le message d'origine pour voir si vous rencontrez le même problème.

    Vous devez faire attention, l'exemple de code AWS (en Objective-C) a deux controllers de navigation, et le code les réutilise. Je n'aime pas la façon dont l'application exemple clignote le controller de vue connecté avant que le délégué d'authentification ne commence et j'essayais d'améliorer cela dans la version rapide et cela a causé mon problème.

    AWS User Pools API est configuré pour fonctionner avec une structure de storyboard ou d'application qui fonctionne comme ceci:

    1) Votre application ASSUMES est connectée, puis triggers le délégué qui triggers l'authentification et les écrans de connection si ce n'est pas le cas.

    2) Dans le controller de vue d'origine connecté pool.currentUser () n'est PAS suffisant pour que l'authentification se fasse, l'API ne triggersra le délégué que lorsque vous en ferez plus (dans mon cas user.getDetails ()).

    3) L'authentification est complétée par le didCompletePasswordAuthenticationStepWithError. Cette méthode de délégué est appelée si vous obtenez une erreur d'authentification (ou autre) ET si vous vous authentifiez SUCCESSFLY. Dans le cas d'une authentification réussie, NSError est nul, donc il devrait être déclaré comme NSError? dans le délégué (cela provoque un avertissement). L'API est bêta, ils vont probablement corriger cela.

    4) Un autre petit "gotcha", il peut être évident pour vous, il m'a attrapé, lorsque vous définissez votre pool d'users dans la console, vous spécifiez les applications autorisées, et chacune de ces applications a DIFFÉRENT STRINGS pour les strings d'ID client. (Je ne faisais que twigr la même chose dans l'exemple) qui fonctionne mal (mais ne signale pas les erreurs). L'API nécessite un travail dans le département de reporting. Il est très verbeux quand il fonctionne, mais ne dit rien si vous lui passez les mauvaises strings client. Aussi, il semble ne rien dire si vous (comme je l'ai fait) appelez l'API de différents viewcontrollers. Il prenait juste chaque nouvelle request d'authentification d'un autre viewcontroller et ne disait rien.

    Quoi qu'il en soit, cela fonctionne maintenant. J'espère que cela aidera à résoudre votre problème.

    Mettre à jour:

    J'ai finalement obtenu getPasswordAuthenticationDetails à exécuter.

    Il s'avère qu'il n'est pas exécuté avant user.getDetails pour l'user actuel (même s'il n'y a pas d'user actuel).

    Alors

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

    entraînera l'exécution du callback getPasswordAuthenticationDetails sur la deuxième ligne.

    Il semble que le concept AWS UserPool est que nous écrivons une application qui suppose que nous avons un user connecté. Nous obtenons des détails de cet user (par exemple dans le controller de vue initiale) et le délégué est expulsé si nous n'avons pas d'user.

    La documentation AWS pour les pools d'users sur IOS manque certaines pages concept importantes. Ces pages sont incluses dans la documentation Android (sinon parallèle). J'avoue que je suis encore aux sockets avec des pools d'users pour travailler rapidement, mais la lecture des parties "Main Classes" et "Key Concepts" de la documentation Android m'a beaucoup éclairé. Je ne vois pas pourquoi il a été omis du doc ​​IOS.

    Merci Elliot. J'essaye d'écrire la version rapide de ce code pendant quelques jours maintenant.

    J'ai essayé d'utiliser le signIn explicite en utilisant le code ci-dessous.

     @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 }) } 

    Lorsque je fournis des informations d'identification correctes, il est renvoyé au bloc d'erreur. Un code de vérification est envoyé à mon mobile chaque fois que j'exécute le code bien que j'ai déjà vérifié mon user lors du process d'inscription. Le corps de réponse est

     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}) 

    Lorsque j'ai fourni un mot de passe incorrect, le corps de la réponse est

     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.}) 

    Pourriez-vous suggérer ce que je fais mal ici?

    J'ai également suivi les mêmes étapes mentionnées dans la question des affiches originales, mais l'application ne passe jamais à l'écran de connection au démarrage. J'ai vérifié que le code pour changer de vue est correct en plaçant directement dans la méthode AppDelegate.application (….). Il semble que la méthode startPasswordAuthentication (…) delete n'est jamais appelée. Quelqu'un peut-il publier un lien vers un exemple d'application qui passe à l'écran de connection à l'aide du protocole AWSCognitoIdentityInteractiveAuthenticationDelegate?

    Juste append mes 2 cents pour les gens qui travaillent avec Objective-c et l'exemple d'application CognitoYourUserPoolsSample fourni par Amazon. @ Bruce0 déjà couvert tout avec sa solution rapide. Mais si vous rencontrez ce problème où getPasswordAuthenticationDetails n'est pas appelé lorsque vous vous connectez, c'est parce que vous n'appelez pas [self.user getDetails] . En effet – getDetails triggers getPasswordAuthenticationDetails. Si vous regardez de plus près dans l'exemple d'application AWS, ils l'appellent correctement lorsqu'ils lancent l'application dans viewDidLoad de UserDetailTableViewController , qui est le premier controller chargé. Si l'user n'est pas connecté, la réponse à getDetails triggers en quelque sorte le SignInViewController. C'est ce que je vais expliquer ci-dessous. C'est comme un "myHomeViewController", où vous voulez afficher des informations liées à l'user. Sinon, vous voulez afficher l'écran de connection / d'inscription par défaut.

    • En règle générale, connectez et initiez le pool d'users Cognito dans votre AppDelegate ( didFinishLaunchingWithOptions ) exactement comme dans l'exemple d'application. Assurez-vous d'append AWSCognitoIdentityInteractiveAuthenticationDelegate et implémentez startPasswordAuthentication à l' endroit où vous allez afficher votre ViewController de connection. Laissez l'AppDelegate s'occuper du WHAT_TO_DO_IF_USER_NOT_SIGNED_IN (par exemple, apportez le SignInViewController en haut), puis concentrez-vous sur le WHEN_DOES_THE_USER_NEEDS_TO_SIGNIN quelque part dans votre application.

    • Lorsque vous avez besoin de données spécifiques à l'user, dites à l'application qu'il est time de vérifier si l'user est connecté (self.user getDetails). Encore une fois, si l'user n'est pas connecté, AppDelegate sait ce qu'il doit faire. Il survit à l'application et affiche la window de connection en plus de tout. Ainsi, il pourrait être juste au début (par exemple Facebook, Twitter, etc.) ou ailleurs (par exemple Ebay, etc.). Appelez simplement [self.user getDetails] à la fin de viewDidLoad. Cela empêchera le ViewController actuel de s'afficher avant l'étape d'authentification (connection / inscription) OU simplement charger le ViewController actuel si l'user est déjà connecté.

    Lorsque vous utilisez la fonctionnalité Pool d'users AWS dans votre application, procédez comme suit:

    1. Découvrez où vous avez besoin de données spécifiques à l'user dans l'un des controllers YourViewController
    2. dans le ViewDidLoad associé, appelez [self.user getDetails]
    3. si l'user est déjà connecté, affichez datatables spécifiques à l'user à l'aide de completionHandler de self.user getDetails.
    4. sinon startPasswordAuthentication est appelée automatiquement dans AppDelegate, ce qui affiche la connection ViewController avant d'afficher YourViewController, car vous avez besoin de données spécifiques à l'user
    5. login ou inscription de l'user
    6. dissmiss inscrivez-vous / inscrivez-vous ViewController et là vous êtes de return dans YourViewController qui peut maintenant être chargé avec certaines données spécifiques à l'user.

    L'exemple d'application AWS n'est pas simple mais c'est vraiment simple.

    Il semble que la méthode "startMultiFactorAuthentication" n'est pas implémentée dans le délégué, c'est pourquoi un mot de passe incorrect est détecté, mais lorsque le mot de passe corrigé est donné, il est escaladé en MFA, mais la fonction MFA n'est pas trouvée dans le délégué. échoue.

    Pour moi j'ai continué à get cette erreur parce que j'ai la vérification sur, mais je n'ai pas vérifié l'user avant d'essayer de se connecter. Une fois vérifié, tout a fonctionné.

    Je suis le même exemple et traite le même problème, et je n'ai pas complètement réussi à le résoudre, mais je soupçonne fortement que le problème a quelque chose à voir avec cette fonction qui ne s'exécute jamais:

     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 } }) } 

    J'ai essayé de mettre des points d'arrêt et des instructions d'printing dans cette méthode et il ne semble jamais être activé. Je suggère de faire la même chose dans votre code, car il semble que votre problème soit identique au mien. J'ai regardé dans l'exemple et j'ai été incapable de find un endroit où la méthode a été appelée manuellement. J'ai remarqué que dans votre code vous initialisez la valeur de passwordAuthenticationCompletion comme ceci:

     var passwordAuthenticationCompletion = AWSTaskCompletionSource() 

    Il semble que getPasswordAuthenticationDetails() est supposé être appelé avant que cette ligne dans la méthode de connection utilise la valeur appropriée: self.passwordAuthenticationCompletion.setResult(AWSCognitoIdentityPasswordAuthenticationDetails(username: emailTextField.text, password: passwordTextField.text))

    J'ai été bloqué pendant un moment en essayant d'aller plus loin, mais je pense toujours que c'est la bonne façon d'implémenter l'logging / connection des users. Il est possible qu'une partie du code de l'exemple ne se traduise pas proprement par Swift, et donc qu'une fonction importante ne soit pas déclenchée en conséquence. Je vais continuer à chercher et mettre à jour ma réponse si je confirme une solution.

    J'ai suivi les mêmes étapes mentionnées précédemment en utilisant Swift et j'ai remarqué que didCompletePasswordAuthenticationStepWithError n'est jamais appelé, bien que LogInViewController étend AWSCognitoIdentityPasswordAuthentication .

    En outre, startPasswordAuthentication() n'est pas appelé dans le délégué même si le délégué implémente également AWSCognitoIdentityInteractiveAuthenticationDelegate .

    Je me request si cela est un problème avec l'implémentation de Swift puisque l'exemple Objective-C qu'Amazon fournit fonctionne bien.

    Je pense qu'il y a un problème dans ceci:

    Object-c ->

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

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

    et parce qu'il y a quelque chose de faux dans l'instruction précédente, la méthode "didCompletePasswordAuthenticationStepWithError" n'a pas été lancée.

    J'ai essayé beaucoup de choses sans chance 🙁 – J'ai essayé d'implémenter tout dans Swift (pas de travail) – J'ai essayé d'append les files Object-C à mon projet basé sur Swift (pas de travail)

    Donc, je pense que je vais utiliser l'échantillon original comme mon démarreur pour mon projet.

    Mettre à jour:

    J'implémente le SignInViewController & MFAController dans Swift et j'importe ces files Swift dans un projet basé sur Object-C. et ça marche bien! Donc, maintenant je suis sûr qu'il y a un problème ou un bug quand nous essayons d'implémenter les protocoles "AWSCognitoIdentityPasswordAuthentication" && "AWSCognitoIdentityMultiFactorAuthentication" dans le projet basé sur Swift. La seule solution que j'ai trouvé est d'aller avec le projet basé sur Object-c.

    C'est ainsi que je l'ai fait dans Swift, avec un seul ViewController et sans rien configurer dans 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") } }