web-dev-qa-db-ja.com

iOS(Swift)アプリのAWS Cognitoユーザープール

IOS(Swift)アプリに新しいAWS Cognitoユーザープールを実装しようとしていますが、サインインプロセスを機能させるのに苦労しています。私は基本的に、使用可能な例 here に従うようにしています。

これは私がこれまでに持っているものです:

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

ログインボタンを押しても何も起こらないようですが、もう一度押すとNSInternalInconsistencyExceptionが表示されます(AWSTaskの結果が既に設定されているためです)。

これで何か助けていただければ幸いです。 AWS SDK for iOSバージョン2.4.1を使用しています。

UPDATE:

私の元の問題の解決策ではありませんが、デリゲートメソッドではなく明示的なサインインメソッドを使用してユーザープールを機能させることができました(詳細については、この ページ を参照してください)。これが私の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
        }
    }
}

次に、AWSサービス(私の場合はCognitoとは異なるリージョンにあります)を使用するために、ユーザープールを使用して新しい認証情報プロバイダーを作成しました。

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

もう1つの問題は、アプリを起動するたびに「有効な「AWSDefaultRegionType」、「AWSCognitoRegionType」、および「AWSCognitoIdentityPoolId」の値がinfo.plistで見つかりませんでした」というエラーが表示されたことです。これは、クラッシュの追跡に使用しているファブリックに関連しているようです。 AppDelegateでこの行を変更することで、これを解決しました。

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

これに:

Fabric.with([Crashlytics.self])

これが他の誰かの役に立つことを願っています。

14
Elliot

更新6:(そして今回は最終的に)

AWSが(ついに)AWS Mobile HubにユーザープールをSignInProvider(GoogleとFacebookも含む)として含む非常に素晴らしいデモアプリを構築させたことは注目に値します。アーキテクチャは(私の意見では)優れています(Identity Managementを分離し、認証から資格情報を取得しています)。

アップデート5:(および最終)

かなり完全な実装例と、この他の回答でどのように機能するかのドキュメントがあります。

iOS-AWS MobileHubは開発者認証プロバイダーでサインイン

アップデート4:

AWSサービスにアクセスする場合は、さらに多くの手順が必要です

これでは、Cognito Federated Identitiesで認証されないことがわかります(IDブラウザーの「ログイン」数は0のままです)。これを修正するには、credentialsProviderを確立し、「credentialsProvider.getIdentityId」を実行する必要があります。その後、ログインは肯定的に表示され、認証されたロールに基づいてAWSからサービスを取得できます。

モバイルアプリに対して認証済みアクセスと未認証アクセスの両方を実行しようとしている場合は、AWSAnonymousCredentialsProviderを(別のサービス構成で)作成する必要があります。次に、ログアウトするときにself.credentialsProvider?.invalidateCachedTemporaryCredentials()およびself.credentialsProvider?.clearCredentials()を実行し、匿名サービス構成でgetidentityidを再度実行すると、匿名IDが取得されます。 (注:credentialsProviderのキーチェーンをクリアすると、ユーザーがログアウトするたびに新しいIDで開始されるように見えたため、無料の50,000個のIDをすぐに使い切ることができました。)

更新3:

IOS for SwiftのAWSユーザープール用のgithubサンプルアプリをアップロードしました。

https://github.com/BruceBuckland/signin

アップデート2:

私はついにAWSユーザープールをSwiftで正しく動作させるようにしました

私の問題は、認証の開始が発生するたびに、別のビューコントローラーでの認証の失敗(エラー)が原因でした。私は結局それらが来ることのない完了リターンを待って実行しているそれらの束で終わり、APIは「サイレント」でした(エラーは表示されませんでした)。 APIは、(毎回異なるviewControllerによって)複数回開始されていることを認識しないため、サイレントに繰り返しログインできます。元の投稿には、同じ問題が発生しているかどうかを確認するのに十分なコードがありません。

注意が必要です。AWSサンプルコード(Objective-C)には2つのナビゲーションコントローラーがあり、コードはそれらを再利用します。認証デリゲートが実行される前にサンプルアプリがログイン済みのビューコントローラーをフラッシュする方法が気に入らないので、Swiftバージョンでそれを改善しようとしていたため、問題が発生しました。

AWS User Pools APIは、次のように機能するストーリーボードまたはアプリ構造で機能するように設定されています。

1)アプリはログインしていると想定し、そうでない場合は認証とログイン画面をトリガーするデリゲートをトリガーします。

2)最初にログインしたビューコントローラーで、pool.currentUser()は認証を実行するのに十分ではなく、APIはさらに実行した場合にのみデリゲートをトリガーします(私の場合はuser.getDetails())。

3)認証は、didCompletePasswordAuthenticationStepWithErrorによって完了します。このデリゲートメソッドは、認証(またはその他の)エラーが発生し、認証が成功した場合に呼び出されます。認証が成功した場合、NSErrorはnilなので、NSErrorとして宣言する必要がありますか?デリゲート(これにより警告が発生します)。 APIはベータ版であり、おそらくこれを修正するでしょう。

4)もう1つ「わかりにくい」ことは明らかです。コンソールでユーザープールを定義するときに、許可するアプリを指定すると、これらのアプリごとにクライアントID文字列の文字列が異なります。 (私はちょうど同じことを例に差し込んでいました)それはうまくいきません(しかしエラーを報告しません)。 APIはレポート部門でいくつかの作業を必要とします。動作しているときは非常に詳細ですが、間違ったクライアント文字列を渡しても何も言われません。また、あなたが(私がしたように)異なるビューコントローラからAPIを呼び出しても何も言われないようです。異なるビューコントローラーから新しい認証リクエストをそれぞれ受け取り、何も言わなかっただけです。

とにかく、それは今動作します。これが問題の解決に役立つことを願っています。

更新:

ようやくgetPasswordAuthenticationDetailsを実行しました。

現在のユーザーのuser.getDetails(現在のユーザーがいない場合でも)になるまで実行されないことがわかります。

そう

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

その結果、2行目でgetPasswordAuthenticationDetailsコールバックが実行されます。

AWS UserPoolのコンセプトは、ログインしたユーザーがいることを想定したアプリを作成することです。そのユーザー(たとえば、最初のビューコントローラー)から詳細を取得し、ユーザーがいない場合はデリゲートを開始します。

IOSのユーザープールに関するAWSドキュメントには、いくつかの重要な概念ページがありません。これらのページは、(そうでなければパラレル)Androidドキュメントに含まれています。私はユーザープールをSwiftで機能させるのに(今でも数日)苦労していますが、Androidのドキュメントの「主なクラス」と「主要な概念」の部分を読んだことで、多くのことがわかりました。 IOS doc。

16
Bruce0

Objective-cとサンプルアプリCognitoYourUserPoolsSampleをAmazonが提供している人に使用する2セントを追加するだけです。 @ Bruce0はすでに彼のSwiftソリューションですべてをカバーしています。しかし、この問題が発生している場合getPasswordAuthenticationDetailsがサインインをヒットしたときに呼び出されないのは、 [self.user getDetails]を呼び出しているわけではありません。確かに-getDetailsはgetPasswordAuthenticationDetailsをトリガーします。AWSサンプルアプリをよく見ると、AWSのviewDidLoadでアプリを起動するとすぐに呼び出されますserDetailTableViewController、これはロードされる最初のコントローラーです。ユーザーがサインインしていない場合、getDetails応答がなんらかの理由でSignInViewControllerをトリガーします。これは以下で説明します。「myHomeViewController」のようなものです、ユーザー関連情報を表示する場所。それ以外の場合は、デフォルトでログイン/サインアップ画面を表示します。

  • 一般的な経験則として、AppDelegateのCognitoユーザープール(didFinishLaunchingWithOptions)をサンプルアプリとまったく同じように接続して初期化します。必ずAWSCognitoIdentityInteractiveAuthenticationDelegateを追加し、サインインViewControllerを表示する場所にstartPasswordAuthenticationを実装してください。 AppDelegateにWHAT_TO_DO_IF_USER_NOT_SIGNED_INを処理させ(たとえば、SignInViewControllerを最前面に表示)、アプリのどこかでWHEN_DOES_THE_USER_NEEDS_TO_SIGNINに注目します。

  • ユーザー固有のデータが必要な場合は、ユーザーにサインインしているかどうかを確認するときが来たことをアプリに通知します(self.user getDetails)。繰り返しますが、ユーザーがサインインしていない場合、AppDelegateは何をすべきかを知っています。アプリを無効にし、すべての上にサインインビューを表示します。そのため、最初(Facebook、Twitterなど)または他の場所(Ebayなど)に配置できます。単にviewDidLoadの最後に[[self.user getDetails]を呼び出します。これにより、現在のViewControllerが認証ステップ(サインイン/サインアップ)の前に表示されなくなりますORユーザーが既にサインインしている場合は、現在のViewControllerをロードするだけです。

アプリでAWSユーザープール機能を使用する場合は、次の手順に従います。

  1. YourViewControllersでユーザー固有のデータが必要な場所を見つける
  2. 関連するViewDidLoadで、[self.user getDetails]を呼び出します。
  3. ユーザーが既にサインインしている場合は、self.user getDetailsのcompletionHandlerを使用してユーザー固有のデータを表示します。
  4. そうでない場合は、startPasswordAuthenticationがAppDelegateで自動的に呼び出され、ユーザー固有のデータが必要になるため、YourViewControllerを表示する前にサインインViewControllerが呼び出されます。
  5. ユーザーのサインインまたはサインアップ
  6. サインイン/サインアップのViewControllerを破棄すると、YourViewControllerに戻り、ユーザー固有のデータを読み込むことができます。

AWSサンプルアプリは単純ではありませんが、とても簡単です。

8
Ed.

エリオット、ありがとう。 Swiftこのコードのバージョンを数日間書いています。

以下のコードを使用して、明示的なsignInを使用してみました。

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

正しい資格情報を提供すると、エラーブロックに移動します。サインアッププロセス中にユーザーを確認済みですが、コードを実行するたびに確認コードが携帯電話に送信されます。レスポンスボディは

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

間違ったパスワードを入力すると、応答本文は

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

ここで私が間違っていることを提案できますか?

2
Abhishek Kumar

また、元のポスターの質問で述べたのと同じ手順を実行しましたが、アプリは起動時にログイン画面に切り替わりません。 AppDelegate.application(....)メソッドに直接配置することで、ビューを切り替えるコードが正しいことを確認しました。 startPasswordAuthentication(...)deleteメソッドが呼び出されないようです。 AWSCognitoIdentityInteractiveAuthenticationDelegateプロトコルを使用してログイン画面に切り替えるサンプルアプリへのリンクを誰かが投稿できますか?

2
ranji

メソッド「startMultiFactorAuthentication」がデリゲートに実装されていないようです。そのため、不正なパスワードが検出されますが、正しいパスワードが指定されると、MFAにエスカレートされますが、MFA関数の開始はデリゲートに見つからないため、ログイン失敗します。

1
Hardik Shah

Swiftを使用して前述の同じ手順を実行しましたが、didCompletePasswordAuthenticationStepWithErrorLogInViewControllerを拡張しますが、AWSCognitoIdentityPasswordAuthenticationが呼び出されないことに気付きました。

また、デリゲートもAWSCognitoIdentityInteractiveAuthenticationDelegateを実装していますが、デリゲートではstartPasswordAuthentication()は呼び出されません。

これはSwiftの実装に問題があるのではないかと思います。Amazonが提供するサンプルObjective-Cがこれらすべての機能を備えているからです。

0
user3349763

これは、単一のViewControllerを使用し、AppDelegateで何も設定せずに、Swiftでそれを実現した方法です。

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

}
0
Boris

私は確認を行っているため、このエラーが発生し続けましたが、ログインを試みる前にユーザーを確認していません。それが確認されると、すべてが機能しました。

0
user3048652

私はこれに問題があると思います:

オブジェクト-c->

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

スウィフト-> self.passwordAuthenticationCompletion.setResult(AWSCognitoIdentityPasswordAuthenticationDetails.init(username: username, password: password))

また、前のステートメントに問題があるため、「didCompletePasswordAuthenticationStepWithError」メソッドは起動しませんでした。

私は運なしで多くのことを試しました:(-Swiftにすべてのものを実装しようとしました(動作しません)-SwiftベースのプロジェクトにObject-Cファイルを追加しようとしました(そうではありません)作業)

それで、私は私のオリジナルのサンプルを私のプロジェクトのスターターとして使用すると思います。

更新:

SignInViewControllerとMFAControllerをSwiftに実装し、これらのSwiftファイルをObject-Cベースのプロジェクトにインポートします。そしてそれはうまくいきます!ですから、Swiftベースのプロジェクトに「AWSCognitoIdentityPasswordAuthentication」と「AWSCognitoIdentityMultiFactorAuthentication」プロトコルを実装しようとすると、問題またはバグがあると確信しています。 私が見つけた唯一の解決策は、Object-cベースのプロジェクトを使用することです。

0
Alharbi

私は同じ例に従って同じ問題を処理していますが、完全に解決することはできませんでしたが、問題はこの関数が実行されなかったことと関係があると強く疑っています。

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

このメソッドにブレークポイントと印刷ステートメントを入れてみましたが、アクティブ化されていないようです。あなたの問題は私のものと同じであるように思われるので、私はあなたのコードで同じことをすることを勧めます。例を調べたところ、メソッドが手動で呼び出された場所が見つかりませんでした。私はあなたのコードでpasswordAuthenticationCompletionの値を次のように初期化していることに気づきました:

_var passwordAuthenticationCompletion = AWSTaskCompletionSource()
_

Loginメソッドのこの行が関連する値を使用する前に、getPasswordAuthenticationDetails()が呼び出されることになっているようです:self.passwordAuthenticationCompletion.setResult(AWSCognitoIdentityPasswordAuthenticationDetails(username: emailTextField.text, password: passwordTextField.text))

私はしばらくこれから先に進もうとしていますが、それでもユーザー登録/ログインを実装するための正しいルートだと思います。例のコードの一部がSwiftに完全に変換されない可能性があるため、一部の重要な関数が結果としてトリガーされていません。引き続き確認し、解決策を確認したら回答を更新します。

0
user3726962