概要
ログインが必要なWebサイトが複数存在し、それぞれでログインページを作成していましたが、ログインを扱う箇所を一つに纏める目的で OAuth2 サーバを構築することになりました。
事前調査を兼ねて Laravel9 で構築したので、備忘録としてまとめます。
GitHub: https://github.com/tomy-develop/proto-laravel-9
前提条件
- Apple M1 Max
- macOS Monterey (v12.3)
- Docker Desktop for Mac (v4.5.0)
初期構築
Homebrew のインストール
まずはパッケージマネージャーのHomebrewをインストールします。
~ % bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Docker のインストール
先ほどインストールした Homebrew を使用してDockerをインストールします。
~ % brew install docker
Laravel 9 のインストール
公式サイトに載っている手順に従って、 Laravel の新規アプリケーションを作成するコマンドを実行します。
プロジェクト名は proto-laravel-9
で作成します。
Sail サービスは以下を指定しています。
- pgsql
- redis
- minio
- mailhog
~ % curl -s "https://laravel.build/proto-laravel-9?with=pgsql,redis,minio,mailhog" | bash
https://laravel.build/proto-laravel-9?with=pgsql,redis,minio,mailhog
の中身は以下のようになっており、 Docker コンテナを立ち上げて laravel new
で新しい Laravel アプリケーションを作成しているのがわかります。
docker info > /dev/null 2>&1
# Ensure that Docker is running...
if [ $? -ne 0 ]; then
echo "Docker is not running."
exit 1
fi
docker run --rm \
-v "$(pwd)":/opt \
-w /opt \
laravelsail/php81-composer:latest \
bash -c "laravel new proto-laravel-9 && cd proto-laravel-9 && php ./artisan sail:install --with=pgsql,redis,minio,mailhog "
cd proto-laravel-9
CYAN='\033[0;36m'
LIGHT_CYAN='\033[1;36m'
WHITE='\033[1;37m'
NC='\033[0m'
echo ""
if sudo -n true 2>/dev/null; then
sudo chown -R $USER: .
echo -e "${WHITE}Get started with:${NC} cd proto-laravel-9 && ./vendor/bin/sail up"
else
echo -e "${WHITE}Please provide your password so we can make some final adjustments to your application's permissions.${NC}"
echo ""
sudo chown -R $USER: .
echo ""
echo -e "${WHITE}Thank you! We hope you build something incredible. Dive in with:${NC} cd proto-laravel-9 && ./vendor/bin/sail up"
fi
sail エイリアスの登録
sail
コマンドで実行できるようにエイリアスを登録します。
proto-laravel-9 % alias sail='[ -f sail ] && bash sail || bash vendor/bin/sail'
Docker コンテナの起動
Docker コンテナが起動したら、 ブラウザで http://localhost
にアクセスするとアプリケーションのトップ画面を確認することができます。
proto-laravel-9 % sail up -d
認証機能の実装
Laravel Jetstream のインストール
画面と認証機能をまとめて実装するため Laravel Jetstream を導入します。
proto-laravel-9 % sail composer require laravel/jetstream
Laravel Jetstream の初期設定
画面は Inertia + Vue を使用します。
proto-laravel-9 % sail artisan jetstream:install inertia
proto-laravel-9 % sail artisan migrate
GET|HEAD / ...............................................................................................................
POST _ignition/execute-solution ........ ignition.executeSolution › Spatie\LaravelIgnition › ExecuteSolutionController
GET|HEAD _ignition/health-check .................... ignition.healthCheck › Spatie\LaravelIgnition › HealthCheckController
POST _ignition/update-config ................. ignition.updateConfig › Spatie\LaravelIgnition › UpdateConfigController
GET|HEAD api/user ........................................................................................................
GET|HEAD dashboard ............................................................................................. dashboard
GET|HEAD forgot-password ......................... password.request › Laravel\Fortify › PasswordResetLinkController@create
POST forgot-password ............................ password.email › Laravel\Fortify › PasswordResetLinkController@store
GET|HEAD login ........................................... login › Laravel\Fortify › AuthenticatedSessionController@create
POST login .................................................... Laravel\Fortify › AuthenticatedSessionController@store
POST logout ........................................ logout › Laravel\Fortify › AuthenticatedSessionController@destroy
GET|HEAD register ........................................... register › Laravel\Fortify › RegisteredUserController@create
POST register ....................................................... Laravel\Fortify › RegisteredUserController@store
POST reset-password .................................. password.update › Laravel\Fortify › NewPasswordController@store
GET|HEAD reset-password/{token} .......................... password.reset › Laravel\Fortify › NewPasswordController@create
GET|HEAD sanctum/csrf-cookie ................................................. Laravel\Sanctum › CsrfCookieController@show
GET|HEAD two-factor-challenge ........ two-factor.login › Laravel\Fortify › TwoFactorAuthenticatedSessionController@create
POST two-factor-challenge ............................ Laravel\Fortify › TwoFactorAuthenticatedSessionController@store
DELETE user ................................... current-user.destroy › Laravel\Jetstream › CurrentUserController@destroy
GET|HEAD user/confirm-password ................... password.confirm › Laravel\Fortify › ConfirmablePasswordController@show
POST user/confirm-password ..................................... Laravel\Fortify › ConfirmablePasswordController@store
GET|HEAD user/confirmed-password-status . password.confirmation › Laravel\Fortify › ConfirmedPasswordStatusController@show
POST user/confirmed-two-factor-authentication two-factor.confirm › Laravel\Fortify › ConfirmedTwoFactorAuthentication…
DELETE user/other-browser-sessions other-browser-sessions.destroy › Laravel\Jetstream › OtherBrowserSessionsController@…
PUT user/password ................................ user-password.update › Laravel\Fortify › PasswordController@update
GET|HEAD user/profile ...................................... profile.show › Laravel\Jetstream › UserProfileController@show
PUT user/profile-information user-profile-information.update › Laravel\Fortify › ProfileInformationController@update
DELETE user/profile-photo .............. current-user-photo.destroy › Laravel\Jetstream › ProfilePhotoController@destroy
POST user/two-factor-authentication .... two-factor.enable › Laravel\Fortify › TwoFactorAuthenticationController@store
DELETE user/two-factor-authentication . two-factor.disable › Laravel\Fortify › TwoFactorAuthenticationController@destroy
GET|HEAD user/two-factor-qr-code ................... two-factor.qr-code › Laravel\Fortify › TwoFactorQrCodeController@show
GET|HEAD user/two-factor-recovery-codes ....... two-factor.recovery-codes › Laravel\Fortify › RecoveryCodeController@index
POST user/two-factor-recovery-codes ................................... Laravel\Fortify › RecoveryCodeController@store
NPM 依存関係のインストール
proto-laravel-9 % sail npm install
コンポーネントのビルド
ビルドが完了したら、ブラウザで http://localhost/register
にアクセスするとユーザ登録画面になります。
ユーザを登録後 http://localhost/login
でメールアドレスとパスワードを入力してログインすることができます。
proto-laravel-9 % sail npm run dev
OAuth2 機能の実装
Laravel Passport のインストール
OAuth2 を機能を使用できる Laravel Passport を導入します。
proto-laravel-9 % sail composer require laravel/passport
Laravel Passport の初期設定
ログイン時に使用するクライアントIDをUUIDで扱いたいので --uuids
をつけて初期設定を行います。
proto-laravel-9 % sail artisan migrate
proto-laravel-9 % sail artisan passport:install --uuids
- use Laravel\Sanctum\HasApiTokens;
+ use Laravel\Passport\HasApiTokens;
+ use Laravel\Passport\Passport;
public function boot()
{
$this->registerPolicies();
+ Passport::routes();
}
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
+ 'api' => [
+ 'driver' => 'passport',
+ 'provider' => 'users',
+ ],
],
GET|HEAD oauth/authorize ........ passport.authorizations.authorize › Laravel\Passport › AuthorizationController@authorize
POST oauth/authorize ..... passport.authorizations.approve › Laravel\Passport › ApproveAuthorizationController@approve
DELETE oauth/authorize .............. passport.authorizations.deny › Laravel\Passport › DenyAuthorizationController@deny
GET|HEAD oauth/clients .............................. passport.clients.index › Laravel\Passport › ClientController@forUser
POST oauth/clients ................................ passport.clients.store › Laravel\Passport › ClientController@store
PUT oauth/clients/{client_id} .................. passport.clients.update › Laravel\Passport › ClientController@update
DELETE oauth/clients/{client_id} ................ passport.clients.destroy › Laravel\Passport › ClientController@destroy
GET|HEAD oauth/personal-access-tokens passport.personal.tokens.index › Laravel\Passport › PersonalAccessTokenController@f…
POST oauth/personal-access-tokens passport.personal.tokens.store › Laravel\Passport › PersonalAccessTokenController@s…
DELETE oauth/personal-access-tokens/{token_id} passport.personal.tokens.destroy › Laravel\Passport › PersonalAccessToke…
GET|HEAD oauth/scopes ..................................... passport.scopes.index › Laravel\Passport › ScopeController@all
POST oauth/token ................................ passport.token › Laravel\Passport › AccessTokenController@issueToken
POST oauth/token/refresh ................ passport.token.refresh › Laravel\Passport › TransientTokenController@refresh
GET|HEAD oauth/tokens ................. passport.tokens.index › Laravel\Passport › AuthorizedAccessTokenController@forUser
DELETE oauth/tokens/{token_id} .... passport.tokens.destroy › Laravel\Passport › AuthorizedAccessTokenController@destroy
ログインリダイレクトの対応
Inertia を使用している場合に redirect()
を使用すると iFrame でリダイレクト先が表示されるという問題が発生しました。
iFrame ではなく通常のリダイレクトにするためには Inertia::location(redirect())
にする必要があります。
OAuth2 のログインリダイレクトでこの現象が発生してしまうので、 Fortify のログイン後の処理をオーバーライドして、正常にリダイレクトするようにします。
+ <?php
+ namespace App\Http\Responses;
+ use Inertia\Inertia;
+ use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
+ use Laravel\Fortify\Fortify;
+ class LoginResponse implements LoginResponseContract
+ {
+ /**
+ * Create an HTTP response that represents the object.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ public function toResponse($request)
+ {
+ return $request->wantsJson()
+ ? response()->json(['two_factor' => false])
+ : Inertia::location(redirect()->intended(Fortify::redirects('login')));
+ }
+ }
+ use App\Http\Responses\LoginResponse;
+ use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
public function register()
{
+ $this->app->singleton(LoginResponseContract::class, LoginResponse::class);
}
トークン取得 API の CORS 許可
Laravel Passport のパスが CORS の許可対象になっていないので、 oauth/token
を追加します。
- 'paths' => ['api/*', 'sanctum/csrf-cookie'],
+ 'paths' => ['api/*', 'sanctum/csrf-cookie', 'oauth/token'],
ユーザ情報取得 API の準備
検証用にユーザ情報を取得する API を追加します。
+ Route::middleware('auth:api')->get('/user', function (Request $request) {
+ return $request->user();
+ });
チーム開発用に整備
チームで開発する場合はこのままだと不便なため、整備していきます。
.env.example の変更
.env.example をコピーしてすぐに使えるようにするため、環境変数例を修正します。
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://proto-laravel-9.test
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=pgsql
DB_HOST=pgsql
DB_PORT=5432
DB_DATABASE=proto_laravel_9
DB_USERNAME=sail
DB_PASSWORD=password
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=memcached
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=hello@example.com
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
既存のアプリケーションの Composer 依存関係のインストール
laravel new
によってアプリケーションを作成した場合は sail
コマンドを使用することができますが、 GitHub からクローンしてくると vender フォルダが存在しないため、別途 composer install
を実行する必要があります。
proto-laravel-9 % docker run --rm \
-u "$(id -u):$(id -g)" \
-v $(pwd):/var/www/html \
-w /var/www/html \
laravelsail/php81-composer:latest \
composer install --ignore-platform-reqs
チーム開発用の環境構築スクリプトの作成
環境構築用のスクリプトを作成します。
スクリプトを実行すると composer install
php artisan key:generate
php artisan passport:keys --force
が実行され、 .env ファイルが作成されます。
#!/usr/bin/env bash
docker run --rm \
-v $(pwd):/opt \
-w /opt \
laravelsail/php81-composer:latest \
bash -c "composer install --ignore-platform-reqs && cp -n .env.example .env && php artisan key:generate && php artisan passport:keys --force"
proto-laravel-9 % bash setup.sh
備考
実際に運用される際は CORS を適切に設定してください。
この記事の OAuth2 サーバを使用したクライアントアプリケーションを作成する記事を書く予定です。