LaravelのPassportを使ってOAuth2の理解を深める


ディップ Advent Calendar 2018の3日目です。

はじめに

先月、Oauth2.0の社内勉強会に参加しました。
その復習も兼ねて、LaravelのAPI認証パッケージ(Passport)を使ってOauth2.0の理解を深めたいと思います。

学習範囲

下記の記事が分かりやすかったです、いいねの数が物語る良記事です。

一番分かりやすい OAuth の説明

「アクセストークンの要求方法とそれに対する応答方法を標準化したものが OAuth 2.0 である」

OAuth 2.0ということで上記が学習対象です。

OAuth 2.0 全フローの図解と動画
RFC 6749(The OAuth 2.0 Authorization Framework)で定義されている4つの認可フローのうち、今回はResource Owner Password Credentials Grantのトークンエンドポイントへのリクエストとレスポンスをやってみます。

準備1 環境構築

Passportが使える環境を作ります。

環境情報

composer.json
{
    "name": "laravel/laravel",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=7.0.0",
        "fideloper/proxy": "~3.3",
        "laravel/framework": "5.5.*",
        "laravel/passport": "~4.0",
        "laravel/tinker": "~1.0",
        "paragonie/random_compat": "2.*"
    },

PHP composerのインストール

Laravelプロジェクトの作成

composer create-project --prefer-dist laravel/laravel SamplePassport

パッケージインストール

※バージョン指定間違えると上手くいかないので注意

composer require paragonie/random_compat:2.*
composer require laravel/passport=~4.0

DB構築

今回はファイルベースで手軽に使えるsqliteを利用します。

DB作成
touch $PROJECT_HOME/database/database.sqlite3

接続情報設定を設定します。
※DB読み込めなかったのでDB_DATABASEは使いません。

.env
DB_CONNECTION=sqlite
#DB_DATABASE=database/database.sqlite3
database.php
        'sqlite' => [
            'driver' => 'sqlite',
            'database' => env('DB_DATABASE', database_path('database.sqlite3')),
            'prefix' => '',
        ],

passport用のマイグレーションが追加されているので実行

テーブル作成
# $PROJECT_HOME
php artisan migrate

tinkerを使えばコマンドラインからDB操作できました。
これでusersテーブルにユーザ情報を作成。
このデータのアイパスをResource Owner Password Credentials Grantで使います。

ユーザデータ作成
php artisan tinker
Psy Shell v0.9.9 (PHP 7.0.32-4+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman
>>>
>>> $user1 = new App\User;
=> App\User {#2882}
>>> $user1->name = 'user1';
=> "user1"
>>> $user1->email = '[email protected]'
=> "[email protected]"
>>> $user1->password = Hash::make('testtest');
=> "$2y$10$8IbtHGsKzlNKyA1i59GQHeTwtwfTYWVz7.uZYGCWR8pbR0HWK2hPm"
>>> $user1->save();
=> true
>>>
>>> $user2 = new App\User;
=> App\User {#2889}
>>> $user2->name = 'user2';
=> "user2"
>>> $user2->email = '[email protected]';
=> "[email protected]"
>>> $user2->password = Hash::make('testtest');
=> "$2y$10$.h5Ha9Iqjo9yEqll9XqjWOdYVXhztFXKizdyAYWNTm0PFAp5eZlSS"
>>> $user2->save();
=> true

テーブルが作成されました。
プレフィックスがoauth_のものがpassportのテーブルのようです。

usersテーブルにデータを挿入しました。

準備2 アプリの設定・修正

Personal access clientとPassword grant clientの生成

Personal access clientはImplicit Grantで使うようです。
Implicit Grantは認可サーバから返された認可画面でアイパスを入力し、認可リクエストを承認→認可サーバの認可エンドポイントにリクエストを送り、アクセストークンが返されるというようなフローのようです。

Password grant clientはResource Owner Password Credentials Grantで使うようです。
こちらはアイパスを入力する画面がアプリで、その後認可サーバのトークンエンドポイントにアイパス付きのトークンリクエストを投げてアクセストークンを受け取るフローのようです。

参考:OAuth 2.0 全フローの図解と動画

passportインストール時に初回生成されます。

> php artisan passport:install
Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client Secret: mG7B3hzZ6mZzapT1SLvcGA43CbrbhfEeCU9tyVTV
Password grant client created successfully.
Client ID: 2
Client Secret: 77JofUiwYzR4Paw2sA5NtLzddA16YvD1qCxdhADF

passport:installは下記コマンドを実行しているようなので、以降は必要に応じて作成しましょう。

php artisan passport:keys
php artisan passport:client --personal
php artisan passport:client --password

ソース修正

認証済みユーザーのトークンとスコープを確認する為にトレイトを追加しヘルパメソッドを使えるようにする。

App\User.php
<?php

namespace App;

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

class User extends Authenticatable
{
    use HasApiTokens;

アクセストークンの発行・失効やクライアントとパーソナルアクセストークンの管理のルート設定

APP\Providers\AuthServiceProvider.php
use Laravel\Passport\Passport;

...

    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();
    }

PassportのTokenGuardを利用してAPI認証が行われるように変更。

'guards' => [
...
        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],

ここまでで3つのテーブルにデータが挿入され、一部ソースの修正をしました。

実施

今回は画面がないのでコマンドで認可サーバ(SamplePassport)へリクエストを送ります。
http://localhost:8000/oauth/tokenにPOSTでリクエストします。
基本的にtoken要求はこのURIが利用されるみたいです。

リクエスト
curl -X POST -H 'Content-Type: application/json' -d '{"grant_type":"password", "client_id":"2", "client_secret":"77JofUiwYzR4Paw2sA5NtLzddA16YvD1qCxdhADF", "username":"[email protected]", "password":"testtest","scope":"*"}' http://localhost:8000/oauth/token

Resource Owner Password Credentials Grantなのでgrant_typeがpassword、usernameとpasswordもリクエストに含めます。
あとは、クライアント認証が行われるので、passport:installで生成したPassword grant clientの情報もリクエストに含めましょう。

中身
{
    "grant_type":"password",
    "client_id":"2", 
    "client_secret":"77JofUiwYzR4Paw2sA5NtLzddA16YvD1qCxdhADF", 
    "username":"[email protected]",
    "password":"testtest",
    "scope":"*"
}

アクセストークンが取得できました。

レスポンス
{
"token_type":"Bearer",
"expires_in":31535999,
"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImY0MzE5YTZjY2ZhMTc1Zjg5Yzk3YTU3YTRhYjI0MTM3MzdlZDE3OTMzZGE1YzJjMWUwYjNiNDliZjM4MWVhMmM2NmY3MWY1NDRkMjcwYjQ0In0.eyJhdWQiOiIyIiwianRpIjoiZjQzMTlhNmNjZmExNzVmODljOTdhNTdhNGFiMjQxMzczN2VkMTc5MzNkYTVjMmMxZTBiM2I0OWJmMzgxZWEyYzY2ZjcxZjU0NGQyNzBiNDQiLCJpYXQiOjE1NDM2Njc2NDMsIm5iZiI6MTU0MzY2NzY0MywiZXhwIjoxNTc1MjAzNjQyLCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.iqFoJMdUBnCOcZBFl9A-EYykmyJPvrQly2-3mrj-h0Sul16k1jumITOI3Tv5jmfowRemhrE5io604HYBgYcIWsjA1DFX0wFhDXtM2bcdA6r4gUjARnOi3XW3TUznsB27PtX5sP24XaZ66fUWcOgohrl_FBlGnO4vWUmExnsB27PtX5sP24XaZ66fUWcOgohrl_FBlGnO4vWUmExF3X7nI1I3LYY0NXoo5ZwR8Ccw-iYDuekX7O83J7WRGV9Lb5WgycF8wpQfO5gMtrSenqZsJFXuSH145tPk3TK0hDc9CsK7C7Y10F-0pqwR8Ccw-iYDuekX7O83Jzw87Ycm8d4rMH4P_kI9IvAZUm-tkJwSr9cdA8qQ7Ps7WRGV9Lb5WgycF8wpQfO5gM_01J0u4xZtdlyHn_JX4t6Wev3zAted0SyP0Bi14s-Oyv8zo2HAD562c_GKRfY3GSrY9doyoepEvoD_DEFQrha_Tz1pZszAgXyyE-fMczw87Ycm8d4rMH4P_kI9IvAZUm-tkJwSr9cdA8q7JW6909OKeQDb_8OO0GfWZhn9YVb4cSlqfytBXx5NzQ7PsDOs7BVLltgVoFZNO-T6bw31qfVch09-7d_xlrRfyIpm3k8tenYQ6rcbnqV2rTVO4wcNGwXGxoD7Ozs_CFbNpI3sevO5whCAg9zuQQie4s-Oyv8zo2HAD562c_GKRfY3GSrY9doyoepEvoD_DELtJB8eHlJyEvY2A91b09f4a073a59d59c1112d6a7155ef4d7551894f0SNITlHyhbG09UARE7lEsD90jiSstdsGtvoGQjNhrqz4Q1U7JW6909OKeQDb_8d9eea0f38590ce95f473646503b7920c05f6f6215cOO0GfWZhn9YVb4cSlqfytBXx5NzIuEo5VB3FeZadhxR04eII8s28VVJTXUEoo4f3e20c9a14f5b0063ab250b23486e8e5ced2a147dKWkYMfbBl8Krb1hl7XlVdP28JeyV2rTVO4wcNGwXGxoD7Ozs_CFbNpI3sevO5c9738d94c4cec86ac89947af6d6f80068873777100whCAg9zu6_o",
"refresh_token":"def50200e0372b0fc446c98f30d38ad64c89d9491b09f4a073a59d59c1112d6a7155ef4d7551894f00f1a6712b2ce17a7cdf19903e6627c05695b2c4da9bb9e102b33b90607b8ad9eea0f38590ce95f473646503b7920c05f6f6215c2c7aa35340221fef9836cd178c825ea61fcd24fcf281d13e8324dd88b19a64f3e20c9a14f5b0063ab250b23486e8e5ced2a147d7f0ccf94470604f636347884284e9344918c209fe823831e3dedfc43702e4c9738d94c4cec86ac89947af6d6f80068873777100b2f38bd5fb7877b8bd44f6ea7c9324d7b0048452cc7dadf8e4241873e671be7bc98fc60f76a8abfc962b65aab49f2274ccc5a3a737c4ded71bf8bf3fed779072eac56d2cd2746a46c1354a7ea104d5e6daab4adf64ebcb387560ae5bf2aef1cc6ccbca128a5471e66da297ec90362bcd43795edc2accb90ff5c777c9d0d2c1a1cd765973451d71bf9bf9eda004026fdecae39b41903a6208c20c569cd5cb025347"
}

migrateで作成したPassport関連テーブルにアクセストークンとリフレッシュトークンの情報が追加されました。
期限日時のデフォルトは1年ですね。

まとめ

  • LaravelのPassportパッケージを使えばoauth2.0の実装が手軽に出来る。 ただ、oauth2.0の理解を厳かにしたまま進めるとうまく使いこなせないと思うのでしっかり学習する必要がありそうだ。
  • TakahikoKawasakiさんはoauthマスター
  • Laravel5.5 passportではSNS認証でよく使われるAuthorization Code Grantの実装フローが記載されている。 パスポートVueコンポーネントを使った画面実装やテストまで出来るらしい、すごいぞLaravel!

参考

https://readouble.com/laravel/5.5/ja/passport.html
https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be
https://qiita.com/TakahikoKawasaki/items/200951e5b5929f840a1f
https://qiita.com/zaburo/items/65de44194a2e67b59061