Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する

Laravel Jetstream は Laravel8から使える新しいパッケージで、Laravel7以前で利用されていた [Laravel UI](https://github.com/laravel/ui) の後継パッケージとなります。

- [Jetstream公式ドキュメント](https://jetstream.laravel.com/1.x/introduction.html)

## Laravel Jetstreamの機能

- ログイン機能
- ユーザー登録機能
- メール検証
- 2要素認証
- セッション管理
- [Laravel Sanctum](https://github.com/laravel/sanctum)によるAPIサポート
  - チーム管理

上記の機能を提供します。

## Laravel Jetstreamの特徴

JetstreamはTailwind CSSを使用して設計されています。
テンプレートとして、[Livewire](https://jetstream.laravel.com/1.x/stacks/livewire.html)または[Inertia](https://jetstream.laravel.com/1.x/stacks/inertia.html)を選択できます。

- Livewireは主にBladeで書く人向け(SEO、OGPが必要)
- Inertiaは主にVueで書く人向け(SPA)

## 環境

- PHP: 7.4.4
- Laravel: 8.1.0
- Node: 14.2.0
- Yarn: 1.22.4
- Laravel Jetstream: 0.6.0

環境としてこちらの記事、リポジトリを参考にします。

- [最強のLaravel開発環境をDockerを使って構築する【新編集版】](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4)
- https://github.com/ucan-lab/docker-laravel

```shell-session
$ git clone git@github.com:ucan-lab/docker-laravel.git
$ cd docker-laravel
$ make create-project
```

http://127.0.0.1

パスワードリセットメールの動作確認をしたい場合は、下記の記事でメールコンテナを追加すると確認できます。

- [Docker × Laravel メールの送信処理をローカルで確認する](https://qiita.com/ucan-lab/items/49e7f4986564f68e95c4)

## Livewire(Blade) インストール

```shell-session
$ make app
$ composer require laravel/jetstream
$ php artisan jetstream:install livewire
$ php artisan migrate
$ exit
```

```shell-session
$ make web
$ yarn install
$ yarn dev
$ exit
```

http://127.0.0.1

![ScreenShot 2020-09-12 4.48.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/de37491f-f27b-33c8-a386-01c7db5749ce.png)

Welcome画面が表示されればok

### 補足: Livewire のファイル差分

```shell-session
$ git status -s
 M .env.example
 M app/Http/Kernel.php
 M app/Models/User.php
 M app/Providers/RouteServiceProvider.php
 M composer.json
 M composer.lock
 M config/app.php
 M config/session.php
 M database/migrations/2014_10_12_000000_create_users_table.php
 M package.json
 M resources/css/app.css
 M resources/views/welcome.blade.php
 M routes/api.php
 M routes/web.php
 M webpack.mix.js
?? app/Actions/Fortify/CreateNewUser.php
?? app/Actions/Fortify/PasswordValidationRules.php
?? app/Actions/Fortify/ResetUserPassword.php
?? app/Actions/Fortify/UpdateUserPassword.php
?? app/Actions/Fortify/UpdateUserProfileInformation.php
?? app/Actions/Jetstream/DeleteUser.php
?? app/Providers/FortifyServiceProvider.php
?? app/Providers/JetstreamServiceProvider.php
?? app/View/Components/AppLayout.php
?? app/View/Components/GuestLayout.php
?? config/fortify.php
?? config/jetstream.php
?? config/sanctum.php
?? database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php
?? database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
?? database/migrations/2020_09_11_191956_create_sessions_table.php
?? public/css/app.css
?? resources/views/api/api-token-manager.blade.php
?? resources/views/api/index.blade.php
?? resources/views/auth/forgot-password.blade.php
?? resources/views/auth/login.blade.php
?? resources/views/auth/register.blade.php
?? resources/views/auth/reset-password.blade.php
?? resources/views/auth/two-factor-challenge.blade.php
?? resources/views/auth/verify-email.blade.php
?? resources/views/dashboard.blade.php
?? resources/views/layouts/app.blade.php
?? resources/views/layouts/guest.blade.php
?? resources/views/profile/delete-user-form.blade.php
?? resources/views/profile/logout-other-browser-sessions-form.blade.php
?? resources/views/profile/show.blade.php
?? resources/views/profile/two-factor-authentication-form.blade.php
?? resources/views/profile/update-password-form.blade.php
?? resources/views/profile/update-profile-information-form.blade.php
?? tailwind.config.js
```

## Inertia(Vue) インストール

```shell-session
$ make app
$ composer require laravel/jetstream
$ php artisan jetstream:install inertia --teams
$ php artisan migrate
$ exit
```

```shell-session
$ make web
$ yarn install
$ yarn dev
$ exit
```

http://127.0.0.1

![ScreenShot 2020-09-12 4.48.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/de37491f-f27b-33c8-a386-01c7db5749ce.png)

Welcome画面が表示されればok

### 補足: Inertia のファイル差分

```shell-session
$ git status -s
 M backend/.env.example
 M backend/app/Http/Kernel.php
 M backend/app/Models/User.php
 M backend/app/Providers/AuthServiceProvider.php
 M backend/app/Providers/RouteServiceProvider.php
 M backend/composer.json
 M backend/composer.lock
 M backend/config/app.php
 M backend/config/session.php
 M backend/database/migrations/2014_10_12_000000_create_users_table.php
 M backend/package.json
 M backend/resources/css/app.css
 M backend/resources/js/app.js
 M backend/resources/views/welcome.blade.php
 M backend/routes/api.php
 M backend/routes/web.php
 M backend/webpack.mix.js
?? backend/app/Actions/Fortify/CreateNewUser.php
?? backend/app/Actions/Fortify/PasswordValidationRules.php
?? backend/app/Actions/Fortify/ResetUserPassword.php
?? backend/app/Actions/Fortify/UpdateUserPassword.php
?? backend/app/Actions/Fortify/UpdateUserProfileInformation.php
?? backend/app/Actions/Jetstream/AddTeamMember.php
?? backend/app/Actions/Jetstream/CreateTeam.php
?? backend/app/Actions/Jetstream/DeleteTeam.php
?? backend/app/Actions/Jetstream/DeleteUser.php
?? backend/app/Actions/Jetstream/UpdateTeamName.php
?? backend/app/Models/Membership.php
?? backend/app/Models/Team.php
?? backend/app/Policies/TeamPolicy.php
?? backend/app/Providers/FortifyServiceProvider.php
?? backend/app/Providers/JetstreamServiceProvider.php
?? backend/app/View/Components/GuestLayout.php
?? backend/config/fortify.php
?? backend/config/jetstream.php
?? backend/config/sanctum.php
?? backend/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php
?? backend/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
?? backend/database/migrations/2020_05_21_100000_create_teams_table.php
?? backend/database/migrations/2020_05_21_200000_create_team_user_table.php
?? backend/database/migrations/2020_09_11_221935_create_sessions_table.php
?? backend/public/css/app.css
?? backend/resources/js/Jetstream/ActionMessage.vue
?? backend/resources/js/Jetstream/ActionSection.vue
?? backend/resources/js/Jetstream/ApplicationLogo.vue
?? backend/resources/js/Jetstream/ApplicationMark.vue
?? backend/resources/js/Jetstream/Button.vue
?? backend/resources/js/Jetstream/ConfirmationModal.vue
?? backend/resources/js/Jetstream/DangerButton.vue
?? backend/resources/js/Jetstream/DialogModal.vue
?? backend/resources/js/Jetstream/Dropdown.vue
?? backend/resources/js/Jetstream/DropdownLink.vue
?? backend/resources/js/Jetstream/FormSection.vue
?? backend/resources/js/Jetstream/Input.vue
?? backend/resources/js/Jetstream/InputError.vue
?? backend/resources/js/Jetstream/Label.vue
?? backend/resources/js/Jetstream/Modal.vue
?? backend/resources/js/Jetstream/NavLink.vue
?? backend/resources/js/Jetstream/ResponsiveNavLink.vue
?? backend/resources/js/Jetstream/SecondaryButton.vue
?? backend/resources/js/Jetstream/SectionBorder.vue
?? backend/resources/js/Jetstream/SectionTitle.vue
?? backend/resources/js/Jetstream/SwitchableTeam.vue
?? backend/resources/js/Jetstream/Welcome.vue
?? backend/resources/js/Layouts/AppLayout.vue
?? backend/resources/js/Mixins/InteractsWithErrorBags.js
?? backend/resources/js/Pages/API/ApiTokenManager.vue
?? backend/resources/js/Pages/API/Index.vue
?? backend/resources/js/Pages/Dashboard.vue
?? backend/resources/js/Pages/Profile/DeleteUserForm.vue
?? backend/resources/js/Pages/Profile/LogoutOtherBrowserSessionsForm.vue
?? backend/resources/js/Pages/Profile/Show.vue
?? backend/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue
?? backend/resources/js/Pages/Profile/UpdatePasswordForm.vue
?? backend/resources/js/Pages/Profile/UpdateProfileInformationForm.vue
?? backend/resources/js/Pages/Teams/Create.vue
?? backend/resources/js/Pages/Teams/CreateTeamForm.vue
?? backend/resources/js/Pages/Teams/DeleteTeamForm.vue
?? backend/resources/js/Pages/Teams/Show.vue
?? backend/resources/js/Pages/Teams/TeamMemberManager.vue
?? backend/resources/js/Pages/Teams/UpdateTeamNameForm.vue
?? backend/resources/views/app.blade.php
?? backend/resources/views/auth/forgot-password.blade.php
?? backend/resources/views/auth/login.blade.php
?? backend/resources/views/auth/register.blade.php
?? backend/resources/views/auth/reset-password.blade.php
?? backend/resources/views/auth/two-factor-challenge.blade.php
?? backend/resources/views/auth/verify-email.blade.php
?? backend/resources/views/layouts/guest.blade.php
?? backend/tailwind.config.js
```

## Screen Shot(Livewire, Inertia 共通)

### welcome

Jetstreamをインストールすることで右上に `Login`, `Register` のメニューが追加されています。

![ScreenShot 2020-09-12 4.48.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/de37491f-f27b-33c8-a386-01c7db5749ce.png)

## register

ユーザー登録画面です。

![ScreenShot 2020-09-12 4.48.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/00dc337a-4451-5521-dfb9-8fb74e0ab8d5.png)

## dashboard

ユーザー登録するとログインされ、ダッシュボードに遷移します。

![ScreenShot 2020-09-12 4.50.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/46ec1468-9a19-8a30-3893-b66afd73a42f.png)

## profile

![スクリーンショット2020-09-12（04.50.40）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/f1b31489-14ae-e75c-dbd8-2362b814967f.png)

## api tokens

![ScreenShot 2020-09-12 4.51.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/5ec07dbd-7598-6a39-98c3-327047e4fee8.png)

## login

![ScreenShot 2020-09-12 4.51.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/62067d4d-535b-31f2-ed4c-96cd46a01545.png)

## password reset

![ScreenShot 2020-09-12 4.51.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/428870de-ce30-7ead-1f31-5c0fc6512ea3.png)

![ScreenShot 2020-09-12 4.55.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/8bbf7233-9290-b601-f1b2-2d9250ff35d2.png)

![ScreenShot 2020-09-12 4.56.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/8fb47199-c186-5169-35af-96cda9acb108.png)

## Screen Shot(Inertia)

### dashboard

![ScreenShot 2020-09-12 8.11.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/ee11ffff-adc8-5512-d7a9-93baa59c9dbc.png)

### team settings

![ScreenShot 2020-09-12 8.12.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/7fbd8b63-9798-3b55-29b7-9ee702dcc62a.png)

### create new team

![ScreenShot 2020-09-12 8.12.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/7a0bc281-4bca-df9e-b195-cd966cb5b1fa.png)

## 2要素認証

profile ページから Two Factor Authenticationを有効にするとQRコードが表示されます。
[Google Authenticator](https://apps.apple.com/jp/app/google-authenticator/id388497605) をインストールし、カメラでQRコード読み込むと2要素認証できます。

![ScreenShot 2020-09-12 8.27.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/33b7d2ed-888e-3733-73c9-b7a91628c21e.png)

login ページを進むと認証コードを求めるページが表示される。

![ScreenShot 2020-09-12 8.29.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/df1f9b56-3878-ee2b-7ee6-cecf7e2cbbd3.png)

ノーコードでここまでできるとは恐れいりますね...