AndroidからAWS Cognitoのユーザプールをさっくり使う

![スクリーンショット 2021-01-21 8.25.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/026507a3-3977-e0d2-c867-46e2c94ed8b3.png)


サインアップやサインイン、パスワード再発行など、ユーザ管理がないアプリケーションを見かけない日はありません
とはいえ自前でバックエンドのシステムを作るのも大変...
でもAmplifyはちょっとリッチすぎる気がする...

そんな私のための備忘録です

下記のことができます

- メールアドレス、パスワードによるサインアップ、ログアウト
- メールアドレス認証
- パスワード再発行

メールアドレスやパスワードはガッツリ個人情報です
その辺りの管理をAWSにお願いできるのはかなり気楽ですよねー

# 前準備
## SES (Simple Email Service)に配信用のメールアドレスを登録する
![0_SESにメールアドレスを登録し、送られたURLをクリックしてVerifyする.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/7962b109-5e3f-1546-a0e8-602fa134ce37.png)

AWSのSESに、認証コードを送信する用のメールアドレスを登録、認証しておきます
登録したメールアドレスに確認用のURLが届くのでクリックすると認証できます
Verification Statusが"verified"になっていれば使用可能です

![1_登録したメールアドレスでテストメールを送信する.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/d6735c35-60cf-0a99-c942-5c30b93c2892.png)

"Send a Test Email"ボタンで自分自身にテストメールを送り、無事受信できればOKです

## Cognitoにユーザプールを作成する

![2_CognitoUserPoolを作成する.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/2462afcf-22b3-cd81-04a5-44bcca3314c1.png)

AWS Cognitoのダッシュボードにアクセスし、UserPoolを作成します
送信元のEメールアドレスに、先ほど認証したメールアドレスを登録し、Amazon SESによるEメール配信を”はい”に指定します

![3_アプリクライアントの認証はとりあえずこんな漢字.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/91e12535-e018-1ff8-0528-f6da4547d61a.png)

アプリクライアント側の設定はこんな感じです

# Androidクライアントを実装する

## 依存関係
依存関係に、下記を追加します

```kotlin:build.gradle
implementation 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.19.0'
```
## いよいよ実装
### 初期化
```kotlin
val userPoolId = "ap-northeast-1_xxxxxxxx"
val clientId = "yyyyyyyyyyyyy"
val clientSecret = "zzzzzzzzzzzzzz"
val userPool = CognitoUserPool(context, userPoolId, clientId, clientSecret, Regions.AP_NORTHEAST_1)
```

先ほど作成したCognitoUserPoolのUserPoolId, ClientId, ClientSecretを使ってインスタンスを作成します
以後、このuserPoolインスタンスを使って各種アクセスをおこないます

### サインアップ
サインアップをするには２つのメソッドが必要です

1. signUpInBackground ： userPoolにユーザを作成する
2. confirmSignUpInBackground : cognitoユーザを認証する

まずはsignUpInBackgroundです
この時点でuserPoolにユーザが追加され、本人確認用コードが送信されます
ただしUNCONFIRMEDの状態で何もできません

```kotlin
fun signUp(username: String, 
           password: String,
           successCallback: () -> Unit, 
           failureCallback: (exception: Exception?) -> Unit
) {
    val userAttributes = CognitoUserAttributes()

    userPool.signUpInBackground(username, password, userAttributes, null,
            object : SignUpHandler {
                override fun onSuccess(user: CognitoUser?, signUpResult: SignUpResult?) {
                    successCallback()
                }

                override fun onFailure(exception: java.lang.Exception?) {
                    failureCallback(exception)
                }

            }
    )
} 
```

このままだとサインインも何もできないので、続いて本人確認をする必要があります
送信された認証用コードを使ってconfirmSignUpInBackgroundを呼ぶとCONFIRMED状態になります

```kotlin
fun confirmSignUp(username: String, 
                  verifyCode: String,
                  successCallback: () -> Unit,
                  failureCallback: (exception: Exception?) -> Unit
) {
    userPool.getUser(username).confirmSignUpInBackground(verifyCode, false,
        object : GenericHandler {
            override fun onSuccess() {
                successCallback()
            }

            override fun onFailure(exception: Exception?) {
                failureCallback(exception)
            }
        })
}
```

### サインイン
サインインにはgetSessionInBackgroundを使用します
一度サインインに成功すると端末内にセッションとして状態が保持されるようです
今回は２段階認証を有効化していないので、getMFACodeなどはコールされない（はず）

```kotlin
fun signIn(username: String, 
           password: String,
           successCallback: () -> Unit,
           failureCallback: (exception: Exception?) -> Unit
) {
    userPool.getUser(username).getSessionInBackground(
        object : AuthenticationHandler {
            override fun onSuccess(userSession: CognitoUserSession?, newDevice: CognitoDevice?) {
                successCallback()
            }
            override fun getAuthenticationDetails(
                authenticationContinuation: AuthenticationContinuation?,
                userId: String?
            ) {
                val authenticationDetails = AuthenticationDetails(userId, password, null)
                authenticationContinuation!!.setAuthenticationDetails(authenticationDetails)
                authenticationContinuation.continueTask()
            }

            override fun getMFACode(continuation: MultiFactorAuthenticationContinuation?) {
                /* should not be called */
            }

            override fun authenticationChallenge(continuation: ChallengeContinuation?) {
                /* should not be called */
            }

            override fun onFailure(exception: Exception?) {
                failureCallback(exception)
            }
        })
}
```

### パスワード再発行
パスワードの再発行にはforgotPasswordInBackGroundを使います
実行するとパスワード再発行用の認証コードがメールアドレスに送られ、getResetCodeコールバックが返ってきます
その時に一緒に得られるcontinuationを使って、新しい認証コードとパスワードを設定すると完了です

認証周りはサインアップと似たようなものかと思いきや、少し違いますね

```kotlin
fun forgotPassword(username: String,
                   successCallback: () -> Unit,
                   verifyCallback: () -> Unit,
                   failureCallback: (exception: Exception?) -> Unit
) {
    userPool.getUser(username).forgotPasswordInBackground(
        object: ForgotPasswordHandler {
            override fun onSuccess() {
                successCallback()
            }

            override fun getResetCode(forgotPasswordContinuation: ForgotPasswordContinuation?) {
                continuation = forgotPasswordContinuation
                verifyCallback()
            }

            override fun onFailure(exception: Exception?) {
                failureCallback(exception)
            }
        })
}

fun verifyPasswordReset(password: String, verifyCode: String) {
    continuation?.apply {
        setPassword(password)
        setVerificationCode(verifyCode)
        continueTask()
    }
}
```

# まとめ
cognito側の仕様にはあまり触れていませんが、これでさっくり使えます！