LoginSignup
2
0

More than 1 year has passed since last update.

Laravel9 + Jetstream + Passport で OAuth2 サーバを構築する

Last updated at Posted at 2022-03-29

概要

ログインが必要な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
app/Models/User.php
 - use Laravel\Sanctum\HasApiTokens;
 + use Laravel\Passport\HasApiTokens;
app\Providers\AuthServiceProvider
+ use Laravel\Passport\Passport;

  public function boot()
  {
      $this->registerPolicies();
+     Passport::routes();
  }
config/auth.php
  '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 のログイン後の処理をオーバーライドして、正常にリダイレクトするようにします。

app/Http/Responses/LoginResponse.php
+ <?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')));
+     }
+ }
app/Providers/FortifyServiceProvider.php
+ 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 を追加します。

config/cors.php
- 'paths' => ['api/*', 'sanctum/csrf-cookie'],
+ 'paths' => ['api/*', 'sanctum/csrf-cookie', 'oauth/token'],

ユーザ情報取得 API の準備

検証用にユーザ情報を取得する API を追加します。

routes/api.php
+ Route::middleware('auth:api')->get('/user', function (Request $request) {
+     return $request->user();
+ });

チーム開発用に整備

チームで開発する場合はこのままだと不便なため、整備していきます。

.env.example の変更

.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 ファイルが作成されます。

setup.sh
#!/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 サーバを使用したクライアントアプリケーションを作成する記事を書く予定です。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0