laravel
PHP7

Laravel5.5でAPI認証のパッケージ(Laravel Passport)を利用する

  • Laravel Passportの動作をmacOSで確認したメモ

環境

macOS Sierra

APIサーバー

  • Vagrant
  • VirtualBox
  • Laravel5.5

https://github.com/niiyz/Laravel-API-Auth-Sample

クライアントサーバー

  • MacのPHPビルトインサーバー

https://github.com/niiyz/Laravel-API-Auth-Client-Server-Sample

MacのPHPバージョンを7.0にする

  • Laravel5.5はPHP7.0以上が必須

brewを使用してPHP7インストール

brew update
brew install homebrew/php/php70  

パス設定

  • シェルの設定に追記後、反映
vi ~/.zshrc
export PATH="$(brew --prefix homebrew/php/php70)/bin:$PATH" <-1行追記する
source ~/.zshrc

確認

  • PHP7.0を確認
php -v                                                                                   
PHP 7.0.22 (cli) (built: Aug  7 2017 14:07:27) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies

APIサーバー(Laravel5.5)

Laravel5.5インストール

composer create-project --prefer-dist laravel/laravel Laravel-API-Auth-Sample dev-develop

プロジェクト直下にHomesteadインストール(vagrant)

composer require laravel/homestead --dev
php vendor/bin/homestead make
  • 起動
vagrant up
  • SSH接続
vagrant ssh

ユーザー認証(通常のユーザー作成準備)

  • ユーザーテーブルSeeder作成
php artisan make:seeder UsersTableSeeder

データ追加処理

  • ユーザーテーブルSeeder追記
database/seeds/UsersTableSeeder.php
<?php

use Illuminate\Database\Seeder;
use App\User;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(User::class)->create([
            'name' => 'テスト',
            'email' => 'test@example.com'
        ]);
    }
}
  • DatabaseSeeder.phpのコメント解除
database/seeds/DatabaseSeeder.php
// コメントアウト
$this->call(UsersTableSeeder::class);

ログイン機能追加

  • ログイン画面用のリソースなどが追加される
php artisan make:auth

API認証

passportインストール

composer require laravel/passport

Provider追記

Laravel PassportパッケージはPackage Auto-Discoveryに対応しているためインストールした際に自動でサービスプロバイダに追加されるのでconfig/app.php記述する儀式が不要になったようです。

config/app.php
/*
 * Package Service Providers...
 */
// Laravel\Passport\PassportServiceProvider::class, // 1行追記

/*
 * Application Service Providers...
 */

DB初期化

php artisan migrate
  • UserSeederに記述したユーザーがusersテーブルに作成される
id         1
name       テスト
email      test@example.com
password   secret
  • OAuth用のテーブルが作成される
oauth_auth_codes
oauth_clients
oauth_personal_acces_clients
oauth_refresh_tokens

OAuth認証のユーザーを作成する

  • 「user ID」 はusers.id を指定する
  • callback用のURLを指定
  • 「Client ID」,「Client secret」がoauth_clientsテーブルに追加される
$ php artisan passport:client

 Which user ID should the client be assigned to?:
 > 1

 What should we name the client?:
 > test

 Where should we redirect the request after authorization? [http://localhost/auth/callback]:
 > http://localhost:3333/callback.php

New client created successfully.
Client ID: 1
Client secret: cZ9W9ysdUFKlowO5d2CzzFFJ1uOBfggjv5Jx5cwr

クライアントサーバー(Macビルドインサーバー)

認証画面を通すやり方

  • よくあるtwitterのアカウントで別サービスにログインする感じのやつ

oAuthサバーへアクセスする

get_token.php
<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client as Client;

$guzzle = new Client;

$query = http_build_query([
    'client_id'     => '1',
    'redirect_uri'  => 'http://localhost:3333/callback.php',
    'response_type' => 'code',
    'scope'         => '',
]);

header('Location: http://192.168.10.10/oauth/authorize?' . $query);

承認画面

スクリーンショット 2017-09-05 20.23.51.png

  • 「Authorize」ボタンを押下するとcodeパラメータ付きでコールバックURLに戻る
  • 「Cancel」ボタン押下するとerrorパラメータ付きでコールバックURLに戻る
  • 承認するとoauth_access_tokens, oauth_auth_codesテーブルにデータが格納される

コールバック

get_token_callback.php
<?php 
require 'vendor/autoload.php';

use GuzzleHttp\Client as Client;

$http = new Client;

if ($_GET['code']) {
    $response = $http->post('http://192.168.10.10/oauth/token', [
        'form_params' => [
            'grant_type'    => 'authorization_code',
            'client_id'     => '1',
            'client_secret' => 'cZ9W9ysdUFKlowO5d2CzzFFJ1uOBfggjv5Jx5cwr',
            'redirect_uri'  => 'http://localhost:3333/callback.php',
            'code'          => $_GET['code'],
        ],
    ]);
    var_dump(json_decode((string)$response->getBody(), true));
}

出力結果

  • 取得したaccess_tokenを使って外部API認証に使う
array(4) {
  ["token_type"]=>
  string(6) "Bearer"
  ["expires_in"]=>
  int(1296000)
  ["access_token"]=>
  string(1071) "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImYxMmE0NDZkNmQxZjBlNzA4MWMwZWEwMzg5MDkxOWI1NWEzYzE5YTMxMGRlMGYwOWQ0ZjA4MmMzYTcxNGU1MzhmOGMzZTk3MzllZTc3OWQ1In0.eyJhdWQiOiIxIiwianRpIjoiZjEyYTQ0NmQ2ZDFmMGU3MDgxYzBlYTAzODkwOTE5YjU1YTNjMTlhMzEwZGUwZjA5ZDRmMDgyYzNhNzE0ZTUzOGY4YzNlOTczOWVlNzc5ZDUiLCJpYXQiOjE1MDQ2MDg2NTcsIm5iZiI6MTUwNDYwODY1NywiZXhwIjoxNTA1OTA0NjU3LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.kbvE4yrfGXcI4Bg0VW10-sDlFpsiG-_UB4uRIi_ylpB46kUkto3JdmlfWLINM83RWNrSkBdnzq6IYfPiHZY1wOw6qwObKG97pWhQVZO9yizVlZCe-ejRO-xv4do0QbXZQHzQqACwhSAYdQwymEJr3JqjrcaNUU8UV5wftlfLsDYNp-NVdo9hvq_7o49B79tIGG7F2izj0mmDHKXfhWLg5s8rFgkHw1q-Wn6o-YCMHgQhfWR9IVvJJ9bpywe45gcvse-MqaaFIQL4q-PHcSLAP0JHMrWLTHy0S2O-D10nWNE2XJyh3VPnxnKLH2jrGZpQVRpUN6RNQlftCxVJEoPruhyeE0Emb5KNJkwvFeIn2hEmLIseXqD4kuk0VESNlCz3VD0r6HRtvfpCTbHBK2uj_l69oYV0yUC6r_D-BfBK6ATZdTk-RIk3HV41ksMqOfpuSPymLoAieq_WqmU0i93VoACwoycirP6z1JTMuT4E1z8i_etgiodcOGpRWZQrePACURCxxGk1xGCaSRMVF_c42WNC5cw10wcHld9FToeFD4-KT_nBryz_eOMJ9Iw6foiyusRbR0vriGExOAesSiLpgLwnDEFBh3YpsDiOEVPvMJolMlVQ6tOnaUwyC3qunjrjVlbF9YR40r1b9DVJ7ZhhWGJNgYg0qadbNGt2uLIYscU"
  ["refresh_token"]=>
  string(706) "def50200ce6524158569f59de11fd955552ae132336954dbd53dffc22544a3af145d1cc0b2b78c36b3c65c238c1486f24b3b551061591a84ec92d68d4438c62de3e6bcffa0df6ce1fa7ea127078a107390b8d3023897eff6cef63064e39b9376ab4ae4e685cc5b9b6b3ed67efe312de73c06458b78c6e7b8e6719d4e11cc9e57315ebb36b0b5e869ac9c0a8ae5f973e12ae2d26f61192d0532c7aeff90e6b7802fc6bd78a7303583bee7f14fabf31486e6431320e6f3d206c61af9dfb482b8195fb0577e9f5cefeb765742400aec2f3f9e16d3b70c5d21732347190bae5287cd13ad01f0b42e1cd3cfc51c193bfce2ffcb5b06f75224c1a7c6498657d2320fd44a78bc047b9bf2bdd17654b69ce9c74280d42bab8be6619be419ee84700d7086ff638a9b715290286906274bccbe9b876afb1b83382b2b2c9840423210e3b5ca4c9cab938391844e261ca6b247e88b3bc96b18d505784c87ef6932c1ed9dec7cc8"
}

認証画面なしでパスワード付きで外部からAPIを実行する

  • 認証画面なしで外部からAPIを通してデータを保存したかったのでパスワード付きの認証にする

OAuth認証のユーザーを作成(APIサーバー)

  • パスワードオプション付きでOAuthユーザー作成
  • usersテーブルのidとは紐付けなくても良いみたい
# php artisan passport:client --password --name=password-user
Password grant client created successfully.
Client ID: 6
Client Secret: IzRQarKQ5y7b8Ni4Sf4nBvlDxeDAujm10lH6r3OW

API作成(APIサーバー)

  • 外部からアクセスしてデータを保存するAPI
routes/api.php
Route::middleware('auth:api')->get('/memo', function (Request $request) {

    $memo = $request->get('memo');

    // SAVE MEMO...

    return json_encode(array_merge(['status' => 'OK'], ['memo' => $memo . '-saved']));
});

クライアントサーバー(Macビルドインサーバー)

  • OAuthユーザーのID, Secretを使用する
  • 併せて通常のLaravelログインユーザーのID, Passwordも使用する
password_auth.php
<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client as Client;

/**
 * Class APIClient
 */
class APIClient {

    /**
     * @var Client
     */
    protected $http;

    /**
     * @var
     */
    protected $accessToken;

    /**
     *
     */
    const API_SERVER = 'http://192.168.10.10';

    /**
     * APIClient constructor.
     */
    public function __construct()
    {
        $this->http = new Client;
    }

    /**
     * @param array $config
     */
    public function auth(array $config = [])
    {

        $response = $this->http->post(self::API_SERVER . '/oauth/token', [
            'form_params' => [
                'grant_type'    => 'password',
                'client_id'     => $config['client_id'],
                'client_secret' => $config['client_secret'],
                'username'      => $config['username'],
                'password'      => $config['password'],
                'scope'         => '',
            ],
        ]);

        $body = json_decode((string)$response->getBody(), true);

        $this->accessToken = $body['access_token'];

    }

    /**
     * @param string $memo
     */
    public function save(string $memo = '')
    {

        $response = $this->http->request('GET', self::API_SERVER . '/api/memo', [
            'headers' => [
                'Accept' => 'application/json',
                'Authorization' => 'Bearer '. $this->accessToken,
            ],
            'query' => ['memo' => $memo],
        ]);

        $body = json_decode((string)$response->getBody(), true);

        if (isset($body['status']) && $body['status'] === 'OK') {
            var_dump('saved memo');
        }
    }
}

// write .env etc
$config = [
    'client_id'     => '6',
    'client_secret' => 'IzRQarKQ5y7b8Ni4Sf4nBvlDxeDAujm10lH6r3OW',
    'username'      => 'test@example.com',
    'password'      => 'secret',
];

$client = new APIClient;

$client->auth($config);
$client->save('ほげ');
// string(10) "saved memo" array(2) { ["status"]=> string(2) "OK" ["memo"]=> string(12) "ほげ-saved" }

参照

https://github.com/laravel/passport
https://laravel.com/docs/5.5/authentication#authenticating-users
https://laravel.com/docs/master/passport#introduction