Laravel Sanctum(旧Airlock)で、Nuxt(TypeScript)と接続する手順メモ
docker-composeの構成はこちらの記事通り
localhost:8080がuiコンテナ(Nuxt)
localhost:8000がapiコンテナ(Laravel)
Laravel Sanctumのインストール
apiコンテナに入る。
1 |
docker-compose exec api sh |
ComposerでLaravel Sanctumをインストール
1 |
composer require laravel/sanctum |
Artisanコマンドを使用してマイグレーションとコンフィグファイルを作成。
1 2 3 4 |
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider" =============== Copied Directory [/vendor/laravel/sanctum/database/migrations] To [/database/migrations] Copied File [/vendor/laravel/sanctum/config/sanctum.php] To [/config/sanctum.php] |
app/Http/Kernel.phpファイル中のapiミドルウェアグループへ、Sanctumのミドルウェアを追加。
1 2 3 4 5 6 7 |
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; // ★追加 'api' => [ EnsureFrontendRequestsAreStateful::class, //★追加 'throttle:60,1', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], |
マイグレーションを実施し、 アクセストークンを保存するpersonal_access_tokensと、usersテーブルを作成する。
1 |
php artisan migrate |
UserモデルでHasApiTokensトレイトを使うように編集する。
1 2 3 4 5 6 |
use Laravel\Sanctum\HasApiTokens; //★追加 class User extends Authenticatable { use HasApiTokens, Notifiable; //★HasApiTokensトレイトを追加 } |
HasApiTokensトレイトは、APIトークン/パーソナルアクセストークンをユーザーに発行する。
ユーザー取得のためのapiルートを、 auth:sanctum Middlewareを通るように変更しておく。
■api/routes/api.php
1 2 3 |
Route::middleware('auth:sanctum')->get('/user', function (Request $request) { return $request->user(); }); |
Laravel側で、ユーザーを追加する。
Laravelのログイン認証機能を使用するため、laravel/uiパッケージをインストール
1 |
composer require laravel/ui |
以下コマンドで、ユーザ登録やログインの画面機能を追加する
1 |
php artisan ui bootstrap --auth |
localhost:8000の右上REGISTERからユーザーを一人登録する。(★登録したら、今後登録機能は外部から入れないようにしておくこと。)
Nuxt.jsの準備
vueファイルをtypescriptでもimportできるようにsfc.d.tsをtsconfig.jsonと同じディレクトリに置きます。(たいていroot直下)
1 2 3 4 |
declare module "*.vue" { import Vue from 'vue' export default Vue } |
Nuxt.js側でaxiosとnuxtjs/authを導入する
※事前に store/index.js という名前で空のファイルを作成しておく(Auth Moduleはvuex storeを使用するため)
yarnで@nuxtjs/axios @nuxtjs/authをインストール
1 |
yarn add @nuxtjs/axios @nuxtjs/auth |
nuxtjs/authの型ファイルもインストール
1 |
yarn add --dev @types/nuxtjs__auth |
nuxt.config.jsに以下を追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
..., axios: { host: 'localhost', // laravelのドメイン port: 8000, //laravelのポート }, auth: { redirect: { login: '/', // 未ログイン時に認証ルートへアクセスした際のリダイレクトURL logout: '/', // ログアウト時のリダイレクトURL callback: false, // Oauth認証等で必要となる コールバックルート home: '/dashboard', // ログイン後のリダイレクトURL }, //strategiesに認証ロジックの情報を記述 strategies: { //localという認証方法を使う場合(その他googleとかの認証も使える。) local: { //axiosでアクセスする際の設定 endpoints: { login: { url: '/login', method: 'post', withCredentials: true, // laravel向けに必要(認証用にクッキーの送信を許可) headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }, logout: { url: '/logout', method: 'post', withCredentials: true, headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' } }, user: { url: '/api/user', method: 'get', propertyName: false, withCredentials: true, headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' } } } } } }, router: { middleware: ['auth'] //Middlewareで、認証状態を確認する。 }, |
各画面で認証状態を確認するMiddlewareを作成しておく。
■ui/middleware/auth.js
1 2 3 4 5 |
export default function({ store, redirect }) { if (!store.state.auth.loggedIn) { return redirect('/') // 未ログインの場合は強制的にTopページへ } } |
■plugins/axios.tsを作成しておく。
参考)Nuxt.jsでaxiosの共通処理を作成し、API呼び出し処理をラップして使用する – Qiita
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import Vue from 'vue' import {AxiosStatic, AxiosInstance} from 'axios' export let repository: AxiosInstance const baseDomain = 'api:8000/api' const baseURL = `http://${baseDomain}` export default function ({$axios}: {$axios: AxiosStatic}) { const api = $axios.create({ baseURL: baseURL, headers: { common: { Accept: 'text/plain, */*' } } }) repository = api return api } |
■ログイン後の、リダイレクト先のページを作っておく。
ui/pages/dashboard.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<template> <v-layout column justify-center align-center> <v-flex xs12 sm8 md6> ログイン完了! </v-flex> </v-layout> </template> <script lang="ts"> import Vue from 'vue'; export default Vue.extend({ }) </script> |
Nuxt.js側で、ログインフォームを作成する。
■/pages/index.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<template> <v-layout column justify-center align-center> <v-flex xs12 sm8 md6> <form class="text-center"> <v-row> <v-text-field v-model="form.email" label="メールアドレス" placeholder="メールアドレス" outlined ></v-text-field> </v-row> <v-row> <v-text-field v-model="form.password" label="パスワード" placeholder="パスワード" type="password" outlined password ></v-text-field> </v-row> <v-btn @click="login" color="primary">ログイン</v-btn> </form> </v-flex> </v-layout> </template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<script lang="ts"> import Logo from '@/components/Logo.vue' import VuetifyLogo from '@/components/VuetifyLogo.vue' import LoginRepository from '@/repositories/AuthRepository' import Vue from 'vue'; import { Auth } from 'nuxtjs__auth' export default Vue.extend({ components: { Logo, VuetifyLogo }, data() { return{ form: { email: '', password: '', } } }, methods: { async login() { try { const auth: Auth = this.$auth await auth.loginWith('local', { data: this.form }); } catch(error) { console.log(error); } }, } }) </script> |
コレで、ログインしようとすると以下のエラーが・・。
CORSですね・・。
今回は Nuxtが8080 番ポートで動いていて、8000 番ポートで動いている API を呼ぶので発生しています。
対応していきましょう!
Laravel側でCORSの設定
api/config/cors.phpを編集する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout'], // api配下と、認証で必要なルートを指定しておく。 'allowed_methods' => ['*'], 'allowed_origins' => ['*'], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => true, // ★trueにする |
セッションクッキードメイン設定をする。
api/config/session.phpを確認。
1 |
'domain' => env('SESSION_DOMAIN', null) |
.envのSESSION_DOMAINを見ているので、追加する(開発環境のため、localhostを指定)
1 |
SESSION_DOMAIN=localhost |
sanctumがNuxtのドメインを認識できるよう設定する。
api/config/sanctum.phpを確認
1 |
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1')) |
,envにSANCTUM_STATEFUL_DOMAINSを追加
1 |
SANCTUM_STATEFUL_DOMAINS=localhost |
apiコンテナ内で以下のコマンドを実行してconfのキャッシュをクリアする(これをしないと、configの値が更新されない場合が多い)
1 |
php artisan config:cache |
確認
ログイン画面にてログインが成功すると、nuxt.config.jsのauth.redirect.homeに設定した、/dashboardページに飛びます。
※loginが成功しているのに302(リダイレクト)になってしまう場合はページ最後の対応を確認。
ログアウト機能作成
bashboardにログアウト機能を作成していきます。
■ui/pages/dashboard.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<template> <v-layout column justify-center align-center> <v-flex xs12 sm8 md6> ログイン完了! <v-btn @click="logout" color="primary">ログアウト</v-btn> </v-flex> </v-layout> </template> <script lang="ts"> import Vue from 'vue'; import { Auth } from 'nuxtjs__auth' export default Vue.extend({ methods: { async logout() { try { const auth: Auth = this.$auth await auth.logout() } catch(error) { console.log(error); } } } }) </script> |
auth.logout()でログアウトしています。
ログアウト完了したら、自動でログイン画面に戻ることを確認。
ユーザー取得機能作成(authではなく通常のaxiosにて。)
nuxt/authではない、通常のaxiosでアクセスできることを確認します。
ダッシュボードにつくっていきます。
1 2 3 4 5 6 7 8 9 |
<template> <v-layout column justify-center align-center> <v-flex xs12 sm8 md6> ログイン完了! <v-btn @click="logout()" color="primary">ログアウト</v-btn> <v-btn @click="getUser()" color="primary">ユーザー情報取得</v-btn> </v-flex> </v-layout> </template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<script lang="ts"> import Vue from 'vue'; import { Auth } from 'nuxtjs__auth' import userRepository from '../repositories/UserRepository' export default Vue.extend({ methods: { async logout() { try { const auth: Auth = this.$auth await auth.logout() } catch(error) { console.log(error); } }, async getUser() { try { const res = await userRepository.getUser() console.log(res) } catch(error) { console.log(error); } } } }) </script> |
■ui/repositories/UserRepository.ts
1 2 3 4 5 6 7 8 9 10 |
import {repository} from '~/plugins/axios' class UserRepository { getUser () { return repository.get('/api/user') } } export default new UserRepository() |
axiosの処理をRepositoryパターンで、共通化しています。
参考)【Vue.js】Web API通信のデザインパターン (個人的ベストプラクティス) – Qiita
ユーザー情報取得ボタンをクリックして動作確認をします。
OK
これでようやく、開発を始められる!!
ログイン時or未ログイン時のレスポンス修正
Laravelをapiとして利用していると、以下の2パターンで、302(リダイレクト)してしまうことがあります。
①未ログイン時orログアウトした後にaxiosリクエストを投げた時
②ログイン時のloginメソッド
例①未ログイン時のaxiosリクエスト(loginページにリダイレクトしてしまう)
例②login時(homeページにリダイレクトしてしまう。)
そんなときは、axiosを投げるときのリクエストヘッダに以下を指定すると401が返ってくるようになる。
1 |
{ "Content-Type": "application/json" } |
プロジェクトの関係で、Laravel側に手を加えなければ行けない場合は、Auth::routes(); のログインルートを上書する手もあります(loginメソッドの変更)
■api/routes/web.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Auth::routes(); Route::post('/login', function( ){ Request::validate([ 'email' => 'required|string', 'password' => 'required|string', ]); $email = Request::get('email'); $password = Request::get('password'); if (Auth::attempt([ 'email' => $email, 'password' => $password ])) { return response()->json('', 204 ); }else{ return response()->json([ 'error' => 'invalid_credentials' ], 403); } }); |
上記のように、Auth::routes();の下に書いてloginルートを上書きします。
※app/Http/Controllers/Auth/LoginController.phpで、AuthenticatesUsersトレイトのloginメソッドをオーバーライドしてもいけるかと思ったのですが、なぜか通らず。。だれかやり方知っている方いたらご教授ください。
ログイン時のaxiosリクエストのリダイレクト先を変更したい場合は、api/app/Http/Kernel.phpのpaiグループに登録した、EnsureFrontendRequestsAreStatefulを差し替える必要があるので、省略します。
———————————
この記事がお役に立てたら、是非シェアをお願いします^^