Laravelで Amazon Cognito ログイン認証2【会員登録機能】
前回はLaravelをベースにAmazon Cognitoと連携したログイン認証を実装し、Cognito側の設定も行いました。
今回はLaravel側の会員登録ページからAmazon Cognitoへ会員登録する機能を実装します。
会員登録時の検証タイプ変更
デフォルトではコードを入力させるタイプの設定になっています。
このままだとユーザ側の手間が少し増えますので、リンクタイプに変更しておきます。
設定はCognito管理コンソールの下記部分です。
全般設定->メッセージのカスタマイズ->Eメール検証メッセージカスタマイズ
会員登録構築の流れ
ログイン機能構築時と同様に、ベースはLaravel認証機能を利用します。
1 | 会員登録画面のバリデーションチェック追加 | ・パスワードポリシーをCognitoに合わせる ・Conigot側の会員重複チェックを行う |
2 | RegisterControllerにCognitoAPIをコールする処理を追加 | ・CognitoClientの準備 |
3 | Cognitoの会員登録APIを実行 | ・会員登録処理時にsignUp APIを実行 |
4 | Laravel側の会員登録も実行 | ・Cognito側の会員登録処理が成功したら、Laravelにも会員を作成 |
会員登録画面のバリデーションチェック追加
必要な作業は下記の2つになります。
- パスワードポリシーをCognitoに合わせる
- Cognito側の会員重複チェックを行う
パスワードポリシーをCognitoに合わせる
Cognitoのパスワードポリシーは下記の通りです。
分かりづらいのですが、要約すると下記です。
- 英数字を含む
- 大文字と小文字をそれぞれ含む
- 記号を含む
これを正規表現でパスワードの入力チェックに加えます。
protected function validator(array $data) { return Validator::make($data, [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed', 'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[\^$*.\[\]{}\(\)?\-\"!@#%&\/,><\':;|_~`])\S{8,99}$/'], ]); }
これでパスワードの入力チェックは完了です。
Cognitoの会員重複チェックを行う
LaravelとCognitoの会員状態は同期していることが利用ですが、何かの不具合で不整合が発生している可能性も考えられます。
Laravel側の会員重複チェックはメールアドレスをベースに実施されていますので、Cognito側も同じようにメールアドレスで重複チェックを行います。
まずはカスタムルールの準備
php artisan make:rule CognitoUserUnique
実装は下記の通りです。
<?php namespace App\Rules; use App\Cognito\CognitoClient; use Aws\CognitoIdentityProvider\CognitoIdentityProviderClient; use Illuminate\Contracts\Validation\Rule; class CognitoUserUnique implements Rule { /** * Create a new rule instance. * * @return void */ protected $config; public function __construct() { $this->config = [ 'region' => config('cognito.region'), 'version' => config('cognito.version') ]; } /** * Determine if the validation rule passes. * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { $client = new CognitoClient( new CognitoIdentityProviderClient($this->config) ); $user = $client->getUserByUsername($value); if($user){ return false; } return true; } /** * Get the validation error message. * * @return string */ public function message() { return 'このメールアドレスはCognitoにすでに登録されています。'; } }
CognitoClientにgetUserByUsernameメソッドを作成し会員を取得します。
取得出来たら重複しているのでfalse(バリデーションNG)を返却し、取得できなければtrue(バリデーションOK)を返却します。
getUserByUsernameメソッドはあとで準備します。
ControllerでCognito会員登録呼び出し
RegisterControllerでは、バリデーションチェックが完了したらcreateメソッドが呼ばれ会員登録処理が実行されます。
ここにCognitoの会員登録APIを呼び出す処理を追加します。
protected function create(array $data) { $config = [ 'region' => config('cognito.region'), 'version' => config('cognito.version') ]; $client = new CognitoClient( new CognitoIdentityProviderClient($config) ); $client->createUser($data['email'],$data['password'],[]); return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => '', ]); }
ここでもCognitoClinetを使って、createUserメソッドを呼び出しています。
createUserメソッドもあとで作成します。
Cognito側の会員登録が問題なく終われば、Laravel側のUser::create処理を実行しています。
Laravel側にはパスワードは不要になるので、空で登録するように変更しています。そのまま入れていても特に問題はございません。
Cognitoの会員登録API実行
CognitoClientに作成が必要なメソッドは2つになります。
- createUser
- getUserByUsername
前回作成したCognitoClientに機能を追加していきます。
public function createUser($email,$password,$attributes){ try { $response = $this->client->signUp([ 'ClientId' => $this->clientId, 'Password' => $password, 'SecretHash' => $this->getSecretHash($email), 'UserAttributes' => $this->formatAttributes($attributes), 'Username' => $email ]); } catch (CognitoIdentityProviderException $e) { if($e->getAwsErrorCode() === 'InvalidPasswordException'){ throw new Exception('パスワードの形式が正しくありません。'); } if($e->getAwsErrorCode() === 'UsernameExistsException'){ throw new Exception('メールアドレスはすでに登録されています。'); } throw $e; } return $response['UserSub']; } private function getSecretHash($username) { $hash = hash_hmac( 'sha256', $username.$this->clientId, $this->client_secret, true ); return base64_encode($hash); } private function formatAttributes(array $attributes) { $userAttributes = []; foreach ($attributes as $key => $value) { $userAttributes[] = [ 'Name' => $key, 'Value' => $value, ]; } return $userAttributes; }
リファレンスに従いリクエストパラメータを設定します。SecretHashというハッシュ値も必須になっておりますので、SecretHash 値の計算を読んで実装します。
エラーが発生した場合はCognitoIdentityProviderExceptionが返却されます。
解説のために「InvalidPasswordException」と「UsernameExistsException」だけキャッチしてメッセージを記載しています。
他にも返却されるエラーコードはありますので、公式サイトの解説を確認しながら必要な処理を実装しましょう。
public function getUserByUsername($username) { try { $user = $this->client->adminGetUser([ 'Username' => $username, 'UserPoolId' => $this->poolId, ]); } catch (CognitoIdentityProviderException $e) { return false; } return $user; }
会員情報の取得にはadminGetUserというAPIを利用します。
メールアドレス検証
Laravelの会員登録画面を利用してもきちんとメールアドレス認証機能は実行されます。
メール宛に届いたリンクをクリックしないままだと、ログインすることはできません。
会員登録時に届いたメールのリンクをクリックするとログインできるようになります。
メールアドレス検証画面も英語のままカスタマイズできないので、別画面にリダイレクトさせるなど対応が必要になりそうです。
まとめ
今回はLaravelを利用したCognito連携第2弾として会員登録機能を構築しました。
Laravel側にベースとなる機能が準備されているため、CognitoのAPIをコールする処理を加えるだけでほとんど実装は完了ですね。
Cognitoを使えばサイト間のSSOも簡単に実現することができます。