Laravel PassportでアクセストークンによるAPI認証

OAuth2サーバの実装を提供するLaravel Passportを使ってアクセストークンによるAPI認証をやってみよう!
Laravel5.8でユーザ認証」まで出来ているところから始めます。

開発環境

Laradock
Laravel 5.8
MySQL 5.7

Laravel Passportインストール

【LaradockのWorkspaceにログイン】

PS C:\laravel\laradock> docker-compose exec workspace bash

【Laravel Passportパッケージ導入】

root@1a1904fc0e8c:/var/www# composer require laravel/passport

【データベース作成】

root@1a1904fc0e8c:/var/www# php artisan migrate

以下のテーブルが作成された。
oauth_auth_codes
oauth_access_tokens
oauth_refresh_tokens
oauth_clients
oauth_personal_access_clients

【暗号キーとクライアントの作成】

root@1a1904fc0e8c:/var/www# php artisan passport:install
Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client secret: KFYSkSXbYyJkAzFd5bd6LP6kniUqQUuSWsEiCuRS
Password grant client created successfully.
Client ID: 2
Client secret: nkSYsl8KYl0mZf1Eplz1YSLq82GC07eG9RyO40ZW

パーソナルアクセスクライアント、パスワードグラントクライアントとそれぞれのアクセストークンの生成に必要な暗号キーが生成されoauth_clientsテーブルに登録される。

Userモデルを修正

Laravel\Passport\HasApiTokensトレイト追加

app/Models/User.php

<?php

namespace App\Models;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;		// 追加

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;		// HasApiTokens追加
}

AuthServiceProviderを修正

AuthServiceProviderのbootメソッドでPassport::routes()を呼ぶことでoauth関連のルーティングが設定される。

app/Providers/AuthServiceProvider.php

use Laravel\Passport\Passport;			// 追記

class AuthServiceProvider extends ServiceProvider
{
    ・・・
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();				// 追記
    }
}

【Routeの確認】

root@1a1904fc0e8c:/var/www# php artisan route:list
+--------+----------+-----------------------------------------+-----------------------------------+---------------------------------------------------------------------------+--------------+
| Domain | Method   | URI                                     | Name                              | Action                                                                    | Middleware   |
+--------+----------+-----------------------------------------+-----------------------------------+---------------------------------------------------------------------------+--------------+
|        | GET|HEAD | /                                       |                                   | Closure                                                                   | web          |
|        | GET|HEAD | api/user                                |                                   | Closure                                                                   | api,auth:api |
|        | GET|HEAD | home                                    | home                              | App\Http\Controllers\HomeController@index                                 | web,auth     |
|        | POST     | login                                   |                                   | App\Http\Controllers\Auth\LoginController@login                           | web,guest    |
|        | GET|HEAD | login                                   | login                             | App\Http\Controllers\Auth\LoginController@showLoginForm                   | web,guest    |
|        | POST     | logout                                  | logout                            | App\Http\Controllers\Auth\LoginController@logout                          | web          |
|        | POST     | oauth/authorize                         | passport.authorizations.approve   | Laravel\Passport\Http\Controllers\ApproveAuthorizationController@approve  | web,auth     |
|        | GET|HEAD | oauth/authorize                         | passport.authorizations.authorize | Laravel\Passport\Http\Controllers\AuthorizationController@authorize       | web,auth     |
|        | DELETE   | oauth/authorize                         | passport.authorizations.deny      | Laravel\Passport\Http\Controllers\DenyAuthorizationController@deny        | web,auth     |
|        | POST     | oauth/clients                           | passport.clients.store            | Laravel\Passport\Http\Controllers\ClientController@store                  | web,auth     |
|        | GET|HEAD | oauth/clients                           | passport.clients.index            | Laravel\Passport\Http\Controllers\ClientController@forUser                | web,auth     |
|        | DELETE   | oauth/clients/{client_id}               | passport.clients.destroy          | Laravel\Passport\Http\Controllers\ClientController@destroy                | web,auth     |
|        | PUT      | oauth/clients/{client_id}               | passport.clients.update           | Laravel\Passport\Http\Controllers\ClientController@update                 | web,auth     |
|        | POST     | oauth/personal-access-tokens            | passport.personal.tokens.store    | Laravel\Passport\Http\Controllers\PersonalAccessTokenController@store     | web,auth     |
|        | GET|HEAD | oauth/personal-access-tokens            | passport.personal.tokens.index    | Laravel\Passport\Http\Controllers\PersonalAccessTokenController@forUser   | web,auth     |
|        | DELETE   | oauth/personal-access-tokens/{token_id} | passport.personal.tokens.destroy  | Laravel\Passport\Http\Controllers\PersonalAccessTokenController@destroy   | web,auth     |
|        | GET|HEAD | oauth/scopes                            | passport.scopes.index             | Laravel\Passport\Http\Controllers\ScopeController@all                     | web,auth     |
|        | POST     | oauth/token                             | passport.token                    | Laravel\Passport\Http\Controllers\AccessTokenController@issueToken        | throttle     |
|        | POST     | oauth/token/refresh                     | passport.token.refresh            | Laravel\Passport\Http\Controllers\TransientTokenController@refresh        | web,auth     |
|        | GET|HEAD | oauth/tokens                            | passport.tokens.index             | Laravel\Passport\Http\Controllers\AuthorizedAccessTokenController@forUser | web,auth     |
|        | DELETE   | oauth/tokens/{token_id}                 | passport.tokens.destroy           | Laravel\Passport\Http\Controllers\AuthorizedAccessTokenController@destroy | web,auth     |
|        | POST     | password/email                          | password.email                    | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail     | web,guest    |
|        | GET|HEAD | password/reset                          | password.request                  | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm    | web,guest    |
|        | POST     | password/reset                          | password.update                   | App\Http\Controllers\Auth\ResetPasswordController@reset                   | web,guest    |
|        | GET|HEAD | password/reset/{token}                  | password.reset                    | App\Http\Controllers\Auth\ResetPasswordController@showResetForm           | web,guest    |
|        | GET|HEAD | register                                | register                          | App\Http\Controllers\Auth\RegisterController@showRegistrationForm         | web,guest    |
|        | POST     | register                                |                                   | App\Http\Controllers\Auth\RegisterController@register                     | web,guest    |
+--------+----------+-----------------------------------------+-----------------------------------+---------------------------------------------------------------------------+--------------+

auth.php修正

認証Guardでapiのドライバーを「token」から「passport」に変更。

config/auth.php

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'passport',		// 変更
            'provider' => 'users',
            'hash' => false,
        ],
    ],

フロントエンド

Passport Vueコンポーネントが用意されているので動作確認だけね。

【Vueコンポーネント生成】

root@1a1904fc0e8c:/var/www# php artisan vendor:publish --tag=passport-components
resources/js/components/passport
   ┣ Clients.vue
   ┣ AuthorizedClients.vue
   ┗ PersonalAccessTokens.vue

【Vueコンポーネント登録】

app.jsに生成されたVueコンポーネントを登録します。

resources/js/app.js

Vue.component(
    'passport-clients',
    require('./components/passport/Clients.vue').default
);

Vue.component(
    'passport-authorized-clients',
    require('./components/passport/AuthorizedClients.vue').default
);

Vue.component(
    'passport-personal-access-tokens',
    require('./components/passport/PersonalAccessTokens.vue').default
);

【Viewにコンポーネントを記述】

とりあえず、HomeController.phpで表示するように。

resources/views/home.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Dashboard</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    You are logged in!

                    {{-- 追記  --}}
                    <passport-clients></passport-clients>
                    <passport-authorized-clients></passport-authorized-clients>
                    <passport-personal-access-tokens></passport-personal-access-tokens>
                    {{-- /追記  --}}

                </div>
            </div>
        </div>
    </div>
</div>
@endsection

【アセットをコンパイル】

root@1a1904fc0e8c:/var/www# npm run dev

> @ dev /var/www
> npm run development


> @ development /var/www
> cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js

sh: 1: cross-env: not found
npm ERR! file sh
npm ERR! code ELIFECYCLE
npm ERR! errno ENOENT
npm ERR! syscall spawn
npm ERR! @ development: `cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js`
npm ERR! spawn ENOENT
npm ERR!
npm ERR! Failed at the @ development script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?

<以下省略>

「sh: 1: cross-env: not found」ってエラーでたね。。。
そもそもnode_moduleが無いじゃない!というわけでインストール

root@1a1904fc0e8c:/var/www# npm install

もう一回!

root@1a1904fc0e8c:/var/www# npm run dev

> @ dev /var/www
> npm run development


> @ development /var/www
> cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js

        Additional dependencies must be installed. This will only take a moment.

        Running: yarn add vue-template-compiler --dev --production=false

↓割と時間かかる・・・

 DONE  Compiled successfully in 9920ms                                                                                                                    2:44:46 PM

       Asset      Size   Chunks             Chunk Names
/css/app.css   173 KiB  /js/app  [emitted]  /js/app
  /js/app.js  1.48 MiB  /js/app  [emitted]  /js/app
root@25295143b25f:/var/www#

ちゃんと動いたっぽい。

【動作確認】

Userログインしてみる

http://localhost/home

laravel_passport1


出ないw

ブラウザのキャッシュクリアで出た!

laravel_passport2

「Create New Client」をクリックしてみる

laravel_passport3
laravel_passport4

oauth_clientsに登録された。

id						3
user_id					3
name					Test
secret					PrNWxYUoxPOGTNVBeic4zQT5QCNiunRLVfzs5I3V
redirect				http://localhost/home
personal_access_client	0
password_client			0

「Create New Token」をクリックしてみる

laravel_passport5
laravel_passport6

oauth_access_tokensに登録された。

id						1f2cdb06b0179dd847d19a8d901608e484817d40dfe96c60fb6abb0a31e7ed58a3ea025b420dc316
user_id		3
client_id	1
name		Test
scopes		[]
revoked		0
created_at	2019-08-21 00:14:54
updated_at	2019-08-21 00:14:54
expires_at	2020-08-21 00:14:54

Personal Access Tokenを利用してユーザ情報取得を取得してみる
PostmanとかAdvanced REST clientとかを使って

GET
http://localhost/api/user
application/json
Authorization	Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjFmMmNkYjA2YjAxNzlkZDg0N2QxOWE4ZDkwMTYwOGU0ODQ4MTdkNDBkZmU5NmM2MGZiNmFiYjBhMzFlN2VkNThhM2VhMDI1YjQyMGRjMzE2In0.eyJhdWQiOiIxIiwianRpIjoiMWYyY2RiMDZiMDE3OWRkODQ3ZDE5YThkOTAxNjA4ZTQ4NDgxN2Q0MGRmZTk2YzYwZmI2YWJiMGEzMWU3ZWQ1OGEzZWEwMjViNDIwZGMzMTYiLCJpYXQiOjE1NjYzMTU5NzcsIm5iZiI6MTU2NjMxNTk3NywiZXhwIjoxNTk3OTM4Mzc3LCJzdWIiOiIzIiwic2NvcGVzIjpbXX0.kA8yR3I5D4wfAcgyz6mIpTt0eL9HZucd6KEKcSdhGS-8OEkBPLqmHIlGWH-w7PhMAtDEN1R7YADSRWiASrrKvbt7WpEOSzifR1C5vFxwu4mNL5wF9jflBXnBJEAra7lSzSpYdOTVOXGBgM7DVcpRlHismGbO2snMtf8hUI8lPaBRtx-7eL_DIIhKcZ1z8GRPo_nVOC1c_sl-5l7m0RS8B9xJX1V4oqkgWYDgWiWp8xBjFcrkRp_L3hOGKkJgB73k1JFJM6DwvTi4leDZj3NnTofOT9rHLueH3ZneawBVATKANyhN0kMsSnG4qthS4biyB2F-IHwGSsq629-T8nXFBC3RQmh1ICcRtUHg76rbXeMsvwSaufbu5wlVcfSYhnT10fM5zxOuvKGNLFHyqj2QXJHstxsFyZI0XvyQXSRWEwj4dkLjJuNz54tkgQD2B1WWByzbWQUfoAE4UUOIoPvNc7rERAUoIJmRccsP02iwyamltASlyDC73qRiFr3Wcv-r9NomDqd67PI98BCER39kPMZnjCgPj6ViROdmbHLYY7ZlfQKv9783Od5ReiIQUeyCBopWFxPA4-fAyEDG--r8QhjK_v9b_h9KBy7lWxFlmtUvJN5MFVO3j_04nWAsSQgQosOvPqAoEEB9WX74zdGDzD0I4GVp9ccuxlLrbWAHSkg
{
"id": 3,
"name": "テストユーザ",
"email": "*****@gmail.com",
"email_verified_at": "2019-08-15 14:21:17",
"created_at": "2019-08-15 14:20:57",
"updated_at": "2019-08-15 14:21:17"
}

取得できたみたいというわけでとりあえず動作確認完了。

感想

アクセストークン自体はどこにも保存されないので紛失したら再発行ということね。
フロントエンドではPersonal Access Tokenの動作確認だけだったが、Password GrantやAuthorization Code Grantはやってみておきたいところ・・・だがまたいずれ。。。