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も簡単に実現することができます。


