LaravelPassportを使用して、Nuxt.jsからのログインと、ログイン後の認証を行う方法
Laravel側設定
composerコマンド実行
Laravel Passprotのインストール
1 |
composer require laravel/passport |
make:authコマンド実行
ログイン、ユーザ登録、パスワードリセットのベースを構築
1 |
php artisan make:auth |
migration実行
make:authコマンドで以下テーブルのマイグレーションファイルが追加されているはずなのでマイグレーション実行
1 |
php artisan migrate |
以下のテーブルが追加されます。
・oauth_access_tokens
・oauth_auth_codes
・oauth_clients
・oauth_personal_access_clients
・oauth_refresh_tokens
これは、vendor/laravel/passport/database/migrations配下のマイグレーションファイルをしている模様
passport:installコマンド実行
以下コマンドで、認証に必要なシークレットキーを生成します。
1 |
php artisan passport:install |
oauth_clientsテーブルに2レコード追加されますが、これは、アクセストークンを生成するために使用する、「パーソナルアクセス」クライアントと「パスワードグラント」クライアントとなります。
UserモデルへLaravel\Passport\HasApiTokensトレイトを追加
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Laravel\Passport\HasApiTokens;//HasApiTokensを追加1 class User extends Authenticatable { use Notifiable,HasApiTokens; //HasApiTokensを追加2 |
このトレイトを追加することで、認証済みユーザのトークンとスコープを確認するためのヘルパメソッドがUserモデルへ提供されます。
routes/api.phpに、Passportによるルーティングを適用します。
1 2 3 4 5 6 7 8 9 10 11 |
<?php use Illuminate\Http\Request; use Laravel\Passport\Passport; //追加 Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); Passport::routes(); //追加 |
このルートにはアクセストークンの発行、アクセストークンの失効、クライアントとパーソナルアクセストークンの管理のルートが含まれます。
公式には、AuthServiceProviderのbootメソッドから、Passport::routesメソッドを呼び出すとありますが、自分の場合、CORSを設定するミドルウェアを作成して、api/~のPrefixURLでアクセスした場合、必ずそこを通るようにしていたため、routes/api.phpにてルーティングを設定しました。
これにより、以下のルーティングが追加されていることが確認できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
php artisan route:list +--------+----------+---------------------------------------------+-----------------------------------+---------------------------------------------------------------------------+--------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+---------------------------------------------+-----------------------------------+---------------------------------------------------------------------------+--------------+ | | GET|HEAD | / | | Closure | web | | | DELETE | api/oauth/authorize | passport.authorizations.deny | Laravel\Passport\Http\Controllers\DenyAuthorizationController@deny | api,web,auth | | | POST | api/oauth/authorize | passport.authorizations.approve | Laravel\Passport\Http\Controllers\ApproveAuthorizationController@approve | api,web,auth | | | GET|HEAD | api/oauth/authorize | passport.authorizations.authorize | Laravel\Passport\Http\Controllers\AuthorizationController@authorize | api,web,auth | | | GET|HEAD | api/oauth/clients | passport.clients.index | Laravel\Passport\Http\Controllers\ClientController@forUser | api,web,auth | | | POST | api/oauth/clients | passport.clients.store | Laravel\Passport\Http\Controllers\ClientController@store | api,web,auth | | | DELETE | api/oauth/clients/{client_id} | passport.clients.destroy | Laravel\Passport\Http\Controllers\ClientController@destroy | api,web,auth | | | PUT | api/oauth/clients/{client_id} | passport.clients.update | Laravel\Passport\Http\Controllers\ClientController@update | api,web,auth | | | POST | api/oauth/personal-access-tokens | passport.personal.tokens.store | Laravel\Passport\Http\Controllers\PersonalAccessTokenController@store | api,web,auth | | | GET|HEAD | api/oauth/personal-access-tokens | passport.personal.tokens.index | Laravel\Passport\Http\Controllers\PersonalAccessTokenController@forUser | api,web,auth | | | DELETE | api/oauth/personal-access-tokens/{token_id} | passport.personal.tokens.destroy | Laravel\Passport\Http\Controllers\PersonalAccessTokenController@destroy | api,web,auth | | | GET|HEAD | api/oauth/scopes | passport.scopes.index | Laravel\Passport\Http\Controllers\ScopeController@all | api,web,auth | | | POST | api/oauth/token | passport.token | Laravel\Passport\Http\Controllers\AccessTokenController@issueToken | api,throttle | | | POST | api/oauth/token/refresh | passport.token.refresh | Laravel\Passport\Http\Controllers\TransientTokenController@refresh | api,web,auth | | | GET|HEAD | api/oauth/tokens | passport.tokens.index | Laravel\Passport\Http\Controllers\AuthorizedAccessTokenController@forUser | api,web,auth | | | DELETE | api/oauth/tokens/{token_id} | passport.tokens.destroy | Laravel\Passport\Http\Controllers\AuthorizedAccessTokenController@destroy | api,web,auth | |
※AuthServiceProviderで、引数無しで、Passport::routesメソッドを呼び出した場合は、apiのPrefixが付きません。もし、mAuthServiceProviderで、任意のルートに変更する場合は以下の記事が参考になります。(CORS対応する場合、ミドルウェアを別途追加しなければいけないので、routeに直接記載したほうがスッキリする気がしますが・・。)
laravel/passportのrouteのprefixをデフォルトのoauth/から任意のルートに変更する【Laravel5.5】 – Qiita
config/auth.phpのguardsのapiのdriverをpassportへ変更
1 2 3 4 5 6 7 8 9 10 11 12 |
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', //変更 'provider' => 'users', //'hash' => false, //削除 ], ], |
これにより、認証のAPIリクエストが送信された時は、パスポートのTokenGuardを使用するようになります。
configのキャッシュを削除しておく
1 |
php artisan config:cache |
Nuxt.js側設定
Laravel PassportではOAuth2.0に沿った4つの認可方法があります。
今回は、ユーザのパスワード情報を利用してトークンの取得する「Password Grant」を使用し、アクセストークンを取得します。
Laravel Passportでは、/oauth/tokenにpostすることでアクセストークンを取得することができます。
まず、ログインフォームを作成します。
■pages/login/index.vue
1 2 3 4 5 6 7 8 9 10 |
<template> <main id="registrationForm"> <h1>ログイン</h1> <label>ユーザー名</label><br> <input v-model="name"><br> <label>パスワード</label><br> <input v-model="password"><br> <button @click="login">ログイン</button> </main> </template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<script> import loginRepository from '../../repositories/LoginRepository' //①API export default { data () { return { name: '', password: '', } }, methods:{ login(){ // v-modelで、ユーザー名と、パスワードを取得する。 const params = { username: this.name, password: this.password, } const response = loginRepository.login(params) //★処理をまとめる。 } } } </script> |
php artisan make:authで、作成した場合、usersテーブルのemail = usernameとなります。こちらは適宜変更可能ですが、今回はこのままでいきます。
画面は以下のようなかんじですね。
また、コンポーネント内でAPIアクセスすると、記述が膨大になり、見通しも悪くなるので、そちらは、LoginRepository.jsに処理をまとめてみました。(★マークの部分)
この設計は、下記の記事を参考にしています。(ファクトリパターンは特に必要なかったので、適用していません。)
Vue.jsでのAPIリクエストをaxios&RepositoryFactoryパターンで実装する
【Vue.js】Web API通信のデザインパターン (個人的ベストプラクティス) – Qiita
■repositories/LoginRepository.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import repository from './Repository' export default { login (params = null) { //※oauth/tokenへpostするデータ const postData = { grant_type: 'password', client_id: '2', client_secret: 'fsadfSSAdas・・・・',//シークレットID username: params.username, password: params.password, } return repository.post(`/oauth/token/`, postData) } } |
※Password Grantを使用する場合、oauth/tokenへpostするデータは以下の通りになります。
・grant_typeは、’password’固定
・client_idは、oauth_clientsテーブルのid(今回は’2’)
・client_secretは、oauth_clientsテーブルのsecret
※client_idと、client_secretは、 passport:install コマンド実行時、oauth_clientsテーブルに、oauth_clients.name=”Laravel Password Grant Client”で、生成されたレコードを参照してください。
・usernameは、usersテーブルのemail(デフォルトの場合)
・password:は、usersテーブルのpassword
■repositories/Repository.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import axios from 'axios' const baseDomain = 'localhost:8000/api' const baseURL = `http://${baseDomain}` const repository = axios.create({ baseURL: baseURL }) repository.interceptors.response.use(function (response) { return response; }, function (error) { return error.response; }); export default repository |
usersテーブルに登録してあるユーザー情報でログインし、成功すると、以下のように、response.data.access_tokenにて、トークンを取得することができます。
■pages/login/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 |
<script> import loginRepository from '../../repositories/LoginRepository' //①API export default { data () { return { name: '', password: '', } }, methods:{ login(){ // v-modelで、ユーザー名と、パスワードを取得する。 const params = { username: this.name, password: this.password, } const response = loginRepository.login(params) //取得したアクセストークンを取得する。 const accessToken = this.getHeaders(response.data.access_token) //クッキーへ保存 document.cookie = `access_token=${accessToken}` } } } </script> |
このトークンをクッキーや、Vuexや、sessionStrageなどに保存しておきます。(上記例ではクッキー)
認証が必要なAPIを投げる時は、取得したアクセストークンをリクエストのAuthorizationヘッダへBearerトークンとして入れます。
そうすることで、laravelのrouteにて、 ->middleware('auth:api') と指定したパスにアクセスすることが出来ます。
■routes/api.php(例)
1 |
Route::get('/user/{id}', 'UserController@getData')->middleware('auth:api'); |
■repositories/UserRepository.js
1 2 3 4 5 6 7 8 |
import Repository,{getAccessToken} from './Repository' export default { getUser (guideId = null) { Repository.defaults.headers.get['Authorization'] = getAccessToken() return Repository.get(`/user/${guideId}`) } } |
■repositories/Repository.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import axios from 'axios' const baseDomain = 'localhost:8000/api' const baseURL = `http://${baseDomain}` const repository = axios.create({ baseURL: baseURL }) repository.interceptors.response.use(function (response) { return response; }, function (error) { // ★ここで、エラーの時の処理をかく(ログインページに戻すとか) return error.response; }); export default repository // 保存しているアクセストークンを取得するメソットを準備 export function getAccessToken() { return `Bearer ${document.cookie.replace(/(?:(?:^|.*;\s*)access_token\s*\=\s*([^;]*).*$)|^.*$/, "$1")}` } |
認証が失敗した場合は、「Status Code: 401 Unauthorized」が返ってくるので、★の箇所で、ログインページに戻したり、リフレッシュトークンなどを保持しておいて、アクセストークンを更新する処理をかいたり、よしなに処理を記載します。
以上で、完了ですが、アクセストークンの取り回しや、認証処理の共通化については、今後も検討し、良い方法があれば、ブログに追記していきたいと思います。
———————
この記事がお役に立てたら、是非シェアをお願いします^^