Cognito で Laravel の認証を実装する
目的
クラスメソッドタイランドの清水です。
本記事では Cognito を使って Laravel で簡単な認証機能を実装します。
認証のシーケンスは以下のようになります。
また、全体の流れを理解するために Laravel のロジックは非常に簡潔になっているので、本番環境で注意が必要な部分は ⚠️ でコメントを書いています。
前提条件・知識
- AWS アカウントを作成済み
- IAM Role, Policy, Cloud9 の環境を作成できる権限がある
- 使いたいAWS アカウントのリージョンで
cdk bootstrap
コマンドを実行済み - ローカル PC に docker, docker-compose をインストール済み
手順
まずは CDK を実行するための環境を Cloud9 で準備します。
Cloud9 の環境を立ち上げる
はじめにCloud9 の環境が使う EC2 インスタンスにアタッチするロールを作成します。
ロールには以下のポリシーを関連付けます。
⚠️ 最小権限ではありません。実際のプロジェクトで使うときは、最小権限を設定したほうが良いです。
AWSCloud9SSMInstanceProfile
: Session Manager で Cloud9 の環境に接続するためです。以下の Inline Policy
:cdk のデプロイに必要な権限、CloudFormation の Stack を Describe 、Cognito のユーザーを作成する権限です。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sts:AssumeRole" ], "Resource": [ "arn:aws:iam::*:role/cdk-*" ] }, { "Sid": "CfnDescribe", "Effect": "Allow", "Action": [ "cloudformation:DescribeStacks" ], "Resource": [ "*" ] }, { "Sid": "CognitoCreateUser", "Effect": "Allow", "Action": [ "cognito-idp:AdminCreateUser" ], "Resource": [ "*" ] } ] }
そして、Cloud9 の環境を作成し、EC2 インスタンスにロールをアタッチします。
ロールのアタッチは以下の記事を参考にしていただければと思います。
Cognito リソースの作成
Cloud9 の環境に接続して、以下のコマンドを実行します。
私が作成した本ブログ用のリポジトリ(https://github.com/yuta-cmth/blog-cognito-laravel)の clone と、必要なパッケージをインストールします。
git clone https://github.com/yuta-cmth/blog-cognito-laravel.git cd blog-cognito-laravel npm i
続いて、CDK で使う環境変数の宣言後、cdk
コマンドを使ってリソースを作成します。
cdk deploy --require-approval never
コマンド実行後、ターミナルで以下のような出力があれば成功です。
✨ Total time: 33.24s
これで Cognito UserPool が作成できました。
以下のコマンドで UserPool にユーザーを作成します。
<your email address>
には自分のメールアドレスを入れます。
export AWS_COGNITO_USER_POOL_ID=$(aws cloudformation describe-stacks --stack-name BlogCognitoLaravelStack --output text --query 'Stacks[0].Outputs[OutputKey==`CognitoUserPoolId`].OutputValue'); aws cognito-idp admin-create-user --user-pool-id $AWS_COGNITO_USER_POOL_ID --temporary-password password --username laravel-test --user-attributes Name=email,Value=<your email address>
ローカル環境で Laravel を立ち上げる
自分のコンピューターで以下のコマンドを実行し、ローカル環境で Laravel と MySQL を立ち上げます。
git clone https://github.com/yuta-cmth/blog-cognito-laravel.git cd blog-cognito-laravel/codes/blog-cognito-laravel export AWS_ACCOUNT_ID=<your account id>; export AWS_REGION=<your region>; export AWS_COGNITO_USER_POOL_ID=$(aws cloudformation describe-stacks --stack-name BlogCognitoLaravelStack --output text --query 'Stacks[0].Outputs[?OutputKey==`CognitoUserPoolId`].OutputValue'); export AWS_COGNITO_CLIENT_ID=$(aws cloudformation describe-stacks --stack-name BlogCognitoLaravelStack --output text --query 'Stacks[0].Outputs[?OutputKey==`CognitoClientId`].OutputValue'); export AWS_COGNITO_HOSTED_UI_DOMAIN=$(aws cloudformation describe-stacks --stack-name BlogCognitoLaravelStack --output text --query 'Stacks[0].Outputs[?OutputKey==`HostedUIDomain`].OutputValue'); export AWS_COGNITO_CLIENT_SECRET=$(aws cognito-idp describe-user-pool-client --user-pool-id $AWS_COGNITO_USER_POOL_ID --client-id $AWS_COGNITO_CLIENT_ID --output text --query "UserPoolClient.ClientSecret"); ./vendor/bin/sail up -d; ./vendor/bin/sail artisan migrate; ./vendor/bin/sail artisan config:clear; ./vendor/bin/sail artisan config:cache;
⚠️ ローカル PC も cloudformation:DescribeStacks
権限が必要になります。
これでCDK で立ち上げたリソースの情報や、AWS アカウント・リージョンを環境変数にセットして、Laravel を起動することができました。
動作確認
http://localhost にアクセスすると、Laravel の Route codes/blog-cognito-laravel/routes/web.php
の 8~16 (以下の部分です)が実行されます。
Route::get('/', function (Request $request) { $username = $request->session()->get('username'); $cu = CognitoUser::find($username); $user = []; if ($username && !empty ($cu?->refresh_token)) { $user['username'] = $username; } return view('home', ['user' => $user]); })->name('home');
今回はセッションに username
というキーが存在し、かつ cognito_users テーブルにセッション username
の値に一致するレコードに refresh_token があればログイン済みと見なし、
以下の HTML テンプレート codes/blog-cognito-laravel/resources/views/home.blade.php
から HTML を生成してブラウザに返します。
⚠️ このログイン済みかどうかの判定は雑です。本来は refresh token の有効期限は過ぎていないか確認したほうがよいです。
@if (empty($user)) <div> Hello, guest. Please login </div> <a href="{{ route('login') }}">Login</a> @else <div> Hello, {{ $user['username'] }} </div> <a href="{{ route('logout') }}">Logout</a> @endif
現在はまだログインしていないので、以下のような画面が表示されます。
ブラウザで「Login」をクリックすると以下の Cognito Hosted UI の画面に遷移するので、以下のように username とパスワードを入れます。
username は laravel-test
で、パスワードは password
です。
すると、パスワードの再設定が促されるので入力します。
入力して提出すると、Cognito からのコールバックを受け付ける `http://localhost/cognito/login-cb` にリダイレクトし、以下の CognitoController.php ロジックを実行します。
やっていることは、
- Cognito のトークンエンドポイントに authorization code を渡して、id token と refresh token を受け取る
- ブラウザのセッション
username
に id token の payload のcognito:username
を入れる - cognito_users テーブルに refresh token を入れる
- 以上で認証処理を完了とし、トップページにリダイレクトする
になります。
⚠️ 事故を防ぐため、id token は保存しないようにします。
もし id token が必要な場面があれば、refresh token で再発行します。このあたりのロジックはアプリケーションによってまちまちかと思います。
また、本来は id token の検証も必要になります。例えば、id token の payload の aud, iss, token_use, iat です。
public function loginCb(Request $request) { $code = $request->query()['code']; $token_endpoint = 'https://' . config('aws.cognito.hosted_ui_domain') . '/oauth2/token'; $client = new \GuzzleHttp\Client(); $response = $client->request('POST', $token_endpoint, [ 'form_params' => [ 'grant_type' => 'authorization_code', 'code' => $code, 'client_id' => config('aws.cognito.client_id'), 'client_secret' => config('aws.cognito.client_secret'), 'redirect_uri' => route('cognito.login-cb'), ], ]); $body = $response->getBody(); $body = json_decode($body, true); $id_token = $body['id_token']; $payload = explode('.', $id_token)[1]; $payload = base64_decode($payload); $payload = json_decode($payload, true); $username = $payload['cognito:username']; $refresh_token = $body['refresh_token']; session()->put('username', $username); CognitoUser::upsert( [ 'username' => $username, 'refresh_token' => $refresh_token, ], uniqueBy: ['username'], update: ['refresh_token'], ); return redirect()->route('home'); }
これで認証が完了したので、トップページにリダイレクトし、以下の画面が表示されます。
ユーザー名が画面に表示され、ユーザーはログインできたことを確認できます。
リソースの削除
CDK で立ち上げたリソースを以下のコマンドで削除します。
cdk destroy --force
最後に
本記事では、大まかCognito で Laravel の認証機能を実装しました。
今後のブログでは、Cognito での認証を活用する方法を紹介していきたいです。
また、このブログで紹介されているソースコードはすべて https://github.com/yuta-cmth/blog-cognito-laravel で確認できます。