這裡,分別會用OAuth Server,以及 OAuth Client 兩個角度來說明 OAuth2 運作機制

一、建立 OAuth Server

安裝laravel

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

cd laravel_oauth

設定資料庫

建立資料庫 ex. laravel_oauth 複製 .env.example 並命名為 .env

加入資料庫連線設定

生成 app key

產生 APP_KEY

php artisan key:generate

修改 session 儲存方式 (可選擇,如果要存在本地就可略過此步驟)

.evn

SESSION_DRIVER=database

建立 session migrate

php artisan session:table

重新 dump

composer dump-autoload

建立 migrate

php artisan migrate

建立 Authentication 架構

php artisan make:auth

建立 OAuth 2 passport

composer require laravel/passport

設定舊版本MySQL Migration

如果安裝的MySql版本較舊,可能會發生錯誤,因此需加入下方修改設定

app/Providers/AppServiceProvider.php

use Illuminate\Support\Facades\Schema;

public function boot()
{
    Schema::defaultStringLength(191);
}

Migrate

將 auth 及 passport 相關 migration 來自動建立資料表

php artisan migrate

生成加密鑰

透過 passport 安裝 encryption keys,這是在產生 access tokens 所需要的 keys 會產生 client 產生 access tokens 所需要的 “personal access” 及 “password grant”,

php artisan passport:install

USER 權限控制設定

安裝完畢後,開啟 app/User.php,並且加入 Laravel\Passport\HasApiTokens 輔助方法

可以讓你檢查使用者的 token 及作用域

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, Notifiable;
    ...

下一步,開啟 app/Providers/AuthServiceProvider.php 並且加入 use Laravel\Passport\Passport 以及在 boot 加入 Passport::routes(); 這個方法可以用來註冊/註銷 user 的 access tokens

app/Providers/AuthServiceProvider.php

<?php
//預設passport 不會過期,如果需要設定使用時間,可以開啟Carbon
//use Carbon\Carbon;
namespace App\Providers;

use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //
        Passport::routes();
        //預設passport 不會過期,如果需要設定使用時間,可以開啟下方兩行
        //Passport::tokensExpireIn(Carbon::now()->addDays(15));
        //Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
    }
}

最後,開啟 config/auth.php 將 api driver 改為 passport 當具有 authentication 的 API 請求時,可以調用 Passport 的 TokenGuard config/auth.php

<?php
...
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            // 'driver' => 'token',
            'driver'=>'passport',
            'provider' => 'users',
        ],
    ],

建立 Front-end 快速啟動 vue 元件

透過下方指令,快速安裝 Vue 元件

php artisan vendor:publish --tag=passport-components

加入 component

開啟 resources/assets/js/app.js line 19 ,加入下方 components 語法

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

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

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

build assets

安裝 npm 以及透過 npm run dev 讓 Webpack 重新發布 assets 變更的項目至 public

如果安裝過程發生問題,請參考這篇,更新 node 相關模組

npm install

npm run dev

建立 OAuth clients 以及 Person access tokens

接下來準備建立相關的 Controller 及 Views 用來顯示 Front-end 快速啟動元件

這裡建立一個 SettingController

php artisan make:controller SettingsController

App\Http\Controllers\SettingController.php

<?php
...
class SettingsController extends Controller
{
    public function __construct()
        {
            $this->middleware('auth');
        }
 
        public function index()
        {
            return view('settings');
        }
}

建立 View

resources\views\settings.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">            
            <passport-clients></passport-clients>
            <passport-authorized-clients></passport-authorized-clients>
            <passport-personal-access-tokens></passport-personal-access-tokens>
        </div>
    </div>
</div>
@endsection 

增加 route

在 routes/web.php 加入新的 Controller

<?php

Route::get('/settings', 'SettingsController@index')->name('settings');

啟用 Server

php artisan serve --port 8000

查看 OAuth Server 介面

前往 settings 路徑

http://127.0.0.1:8000/settings

新增一個 Client user

在前面,我們在 SettingsController 有加入 $this->middleware(‘auth’); 因此,在第一次訪問 settings 頁面,要先創立一個帳號,創立完成後,預設會自動登入

接著前往 http://127.0.0.1:8000/settings

會看到以下畫面,還沒有任何的

我們先試著建立一個 USER,點選 Create New Client

輸入用戶名稱以及驗證後callback的網址

Name: test Redirect URL: http://127.0.0.1:8001/callback

可以在一個帳號登入的情況下,產生多組 client

這些 client id 都會和你的 userid 連結

OAuth Client

接下來我們要開始建立 OAuth Client

並且測試

這裡會透過 laravel + Guzzle 來建立一些HTTP來模擬與 OAuth Server 驗證

安裝 OAuth Client

首先,安裝 laravel client 端

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

cd laravel_oauth_client

設定資料庫

建立資料庫 ex. laravel_oauth_client 複製 .env.example 並命名為 .env

加入資料庫連線設定

生成 app key

產生 APP_KEY

php artisan key:generate

安裝 Guzzle

Guzzle 是一個PHP 的HTTP client,可以透過他來產生HTTP請求

我們會透過這樣的方式,簡單的產生一些驗證請求

composer require guzzlehttp/guzzle

建立驗證機制

驗證機制的流程如圖所示:

大致可以分成三部分: 取得授權(Grant)取得Token取得機密資料

每一部分發送請求時,都需要在Header夾帶驗證所需要的參數,如下:

step 1.Authorization Request

  • response_type: 授權類型
  • client_id: 客戶端ID
  • redirect_uri: 重新導向的URI
  • scope: 授權範圍
  • state: 任意值,將原封不動的返回

step 3.Authorization Grant

  • grant_type: 表示授權類型,這裡使用固定值 authorization_code
  • client_id: 客戶端ID
  • client_secret: 客戶端 secret
  • redirect_uri: 重新導向的URI
  • code: Grant 授權碼
  • state: 任意值,將原封不動的返回

step 5.Access Token

  • Authorization: 須包含 token type(Bearer) 及 token 字串

上述驗證過程,在這裡都直接建構於 laravel_oauth_client 的 routers中:

routes/web.php

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

use Illuminate\Http\Request;

//Authorization Request and Get Authorization Grant
/*
  在一開始,要先傳送 client id 至 OAuth Server 進行審核,
  OAuth Server 會詢問你是否要同意這個 client id app 取得你的 USER 資料
  當你確認授權之後,OAuth Server 就會提供 Authorization Grant 至 redirect_uri 路徑
*/
Route::get('/authorization_request', function () {
        $query = http_build_query([
            'client_id' => 3, // 輸入要測試的 Client ID
            'redirect_uri' => 'http://127.0.0.1:8001/callback',////Get Authorization Grant
            'response_type' => 'code',
            'scope' => ''
        ]);

        return redirect('http://127.0.0.1:8000/oauth/authorize?'.$query);
});

//Authorization Grant and Get Access Token
/*
  取得 OAuth Server 提供的 Authorization Grant 之後,
  此 client id app 要提供 Authorization Grant 以及
  自己的 client id 及 secret number 至 OAuth Server 請求 token
  當OAuth Server驗證 client id、secret number、Authorization Grant 皆通過之後,
  就會提供 token 至 redirect_uri 路徑

*/

//Authorization Grant and Get Access Token
Route::get('/callback', function (Request $request) {
        $response = (new GuzzleHttp\Client)->post('http://127.0.0.1:8000/oauth/token', [
            'form_params' => [
                'grant_type' => 'authorization_code',
                'client_id' => 3, // 輸入要測試的 Client ID
                'client_secret' => 'nhrq2zShxNDH57nGiXsJ4TZ7Q0KoGdF0BfsWAuCf', // 輸入 client secret
                'redirect_uri' => 'http://127.0.0.1:8001/callback',//Get Access token
                'code' => $request->code,
            ]
        ]);

        session()->put('token', json_decode((string) $response->getBody(), true));

        return redirect('/posts');
});


//Sent Access Token and get Protected Resource
/*
  取得 Passport access token之後,想要在 auth server 開始取得 USER資料時,
  都必須在 header 的 Authoriztion 都要設計成 Bearer token 格式  

  接下來,就只要拿著 access token 就能持續取得使用者相關資料
*/
Route::get('/posts', function () {
			  try {
          $response = (new GuzzleHttp\Client)->get('http://127.0.0.1:8000/api/user', 
          [ //Sent Access Token
              'headers' => [
                  'Accept' => 'application/json',
                  'Authorization' => 'Bearer '.session()->get('token.access_token')
              ]
          ]);

          return $response->getBody();
        }catch(Exception $e){
          return $e->getCode().$e->getMessage();
        }
});

啟用 Client Server

php artisan serve --port 8001

執行

首先,前往 127.0.0.8001/authorization_request

Header會夾帶驗證所需的資料並且導向 OAuth server 127.0.0.1:8000/oauth/authorize

首次連結時,會要求先登入

登入完畢之後,就會顯示有一位 test 想要連結你的個人帳號,請點選Authorize,取得 Authorization Grant(授權)

接著就會自動來回往返驗證過程

直到client id app 取得最後的 access token ,就能拿這 access token 向 OAuth Server 取得資料

查看

當有 client 請求取得 passport 且取得 token 之後,可從 OAuth Server 查看目前有經過認證的 APP

http://127.0.0.1:8000/settings

應用

在 OAuth Server 可以透過 middleware(‘auth:api’) 來驗證

例如,我們建立一個 testsController

Route::get('/tests', 'testsController@index')->name('tests')->middleware('auth:api');

接著就能在 TestsController 裡來安全的返回使用者資料

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Auth;//Authentication

class TestsController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function index()
    {
        $user = Auth::user();
        return 'hello this is Protected Resource, user name is '.$user->name;
    }
}

要留意,設計這些API時,要避免機密資料外漏