Cognito UserPool で疲弊したメモ
前提
- 普段はFirebase Authをヘビーユースしてる人間が書いてる
- Email/Passのログインが絡んでくるとUserPoolはめっちゃ大変
- コードはTypeScriptで書いてる
まず普通にイメージしているメアドパスワードログインをしたい場合は👆にしておくのが良いと思う。
- メールアドレスを入力してログインする
- メアド変更できる
もしこれをユーザー名のほうにしていて、signUpの実装時にusernameにuuidなどランダム文字列を渡すように実装していると、すでに example@gmail
でアカウント登録されていても、同じメアドで再度アカウント登録すればuserIDが別のユーザーが作れてしまう。
ただこの設定にしているときの問題は
- Googleログインで example@gmail のアカウントがログインしている
- このときに example@gmail でメアドパスワードでサインアップする
と普通にサインアップできてしまう。UserPool内に同じメアドだけど別のuserIDのユーザーがいることになる。Firebase Authの感覚からするとちょっとありえない。
これを防ぐにはUserPoolのトリガーのPreSignUpに以下のような処理のLambdaを設定する必要がある(JSでごめん)。雑に言うとサインアップ前にLambda起動して、既存ユーザーに同じメアドの人がいたらエラー扱いにする。
'use strict';
const AWS = require('aws-sdk');
const cognitoIdp = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});
exports.handler = function(event, context) {
// check if email is already in use
if (event.request.userAttributes.hasOwnProperty('email')) {
const email = event.request.userAttributes.email;
const params = {
UserPoolId: event.userPoolId,
Filter: 'email = "' + email + '"',
};
cognitoIdp.listUsers(params).promise()
.then (results => {
// if the usernames are the same, dont raise and error here so that
// cognito will raise the duplicate username error
if (results.Users.length > 0 && results.Users[0].Username !== event.userName) {
context.done(Error('A user with the same email address exists'));
return
}
context.done(null, event);
})
.catch (error => {
console.error(error);
context.done(error);
});
}
};
SignUp時にメールアドレスに検証コードが送られ、それを入力するとそのユーザーは使えるようになる。しかし、検証コード入れてないメアドで再度サインアップしようとすると UsernameExistsException
が発生する。このケースはサインインに流して UnconfirmedException
的なやつが出たら検証コード再送して検証コード入力フローに乗せるのが良い。
検証コード入力の際にはusernameまたはemailが必要になるので、メモリの上に載せつつcodeとともにSDKに渡さないといけない。さらにさらに、 検証コードを入力してもログイン状態を作ってくれない ので、サインアップ時に入力されたメアドパスワードをメモリの上に載せつつ、コード検証処理成功したあとに改めてメアドパスワードサインインの処理を走らせる必要がある。なかなか大変。
メアド変更には結構シビアな問題がある。そもそもUserPoolは、サインアップ時はメアドが検証されていないとログイン状態を作れないみたいな感じの実装になっている。ただ、メアド変更のときは新しいメアドに検証コードが送られるものの、普通に新しいメアドでログインなどできてしまう。これはIssueにもなっているが未だ解決されてない。
これに対処するには email_verified
のフラグをみてフロント側で対応する必要がある。ただ、やっかいなのがFacebookやAppleサインインの場合は email_verified
がfalseになってしまっている。そしてここまで調べて僕は諦めてしまいました。
Googleなどの外部IdPでログインしているユーザーがメールアドレスを変更した後にログアウトし再度GoogleログインするとGoogleアカウントのメールアドレスに戻ってしまう。FirebasedではそんなことがないがUserPoolはそういう仕様というふうに捉えている。