iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
自我挑戰組

PHP 沿途的風景系列 第 28

[Day 28] Call API use JWT in Laravel: Quick start

  • 分享至 

  • xImage
  •  

Call API use JWT in Laravel: Quick start

本文承接前文 [Day 27] Call API use JWT in Laravel: 簡介 和 安裝 ,將自制一個 Member model,應用 tymon/jwt-auth 做出 Login/ Logout 功能和應用 attempt() 時,需注意事項。設定上會與 jwt-auth 的 Quick start 稍有不同:

  • Update your Member model (implements JWTSubject, add getJWTIdentifier(), getJWTCustomClaims())
<?php

namespace App\Models;

use Laravel\Sanctum\HasApiTokens;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Member extends Authenticatable implements JWTSubject
// class Member extends Authenticatable 
{
    use HasFactory, HasApiTokens, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
        'mobile',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}
  • Configure Auth guard (config/auth.php)
    • config/auth.php 初始設定 'defaults' 設置 'guard' => 'web' ,这意味著預設的身份驗證 guard 是 Web guard,通常用於傳統的 Web 應用程序,而不是 API。
    • 此時,JWT 使用的是 API 的應用程序 (call api 功能),因此, routes/api.php 設定的 Route 需加上 middleware
    • 如: Route::middleware('auth:api')->get('register' ..略),確保只有通過 API 身份驗證的用户才能訪問 /register
    • 已知本範例做的是『前後端分離』,因此,'defaults' 設置 'guard' => 'api' ,此時默認 guard 為 API, routes/api.php 的 Route:: 無需加上middleware('auth:api')
  • 目前 'defaults' 設置 'guard' => 'api',若使用的 guard 是 member 的驗證方式,則該 Route:: 寫作:
<?php
Route::middleware('auth:member')->apiResource('products', ProductController::class)->only('index', 'show');
  • auth.php 可以設定多個 guard 在 'guards' 內,現下 Call Api ,Route::middleware('auth:member'),本例 auth.php 設定:
    • 'driver' 使用 jwt, 'provider' 參照 members tables
    • 'providers' 使用 members, driver 使用 eloquent (含 Laravel ORM 總總功能) 查尋 使用者訊息
    • 'model' 為 App\Models\User::class ,意味著 Laravel 在身份驗證時,Laravel 會查找名為 members 的 table,使用 App\Models\Member::class 的 model 來查找和驗證使用者。
<?php
return [
    
    'defaults' => [
        'guard' => 'api',
        'passwords' => 'members',
    ],

    'guards' => [
        'member' => [
            'driver' => 'jwt', 
            'provider' => 'members', // members table
        ], 
        
    ],

    'providers' => [
        'members' => [
            'driver' => 'eloquent',
            'model' => App\Models\Member::class,
        ],

    ],

    // ..略
];

Laravel 專案應用 JWT

  • 建立 AuthController: php artisan make:controller AuthController
  • 欲使用 Auth Facade ,記得在 AuthController.php 設定 use Illuminate\Support\Facades\Auth;
  • 應用 JWT 驗證 Login 為例 (詳細可參考 Laravel #Manually Authenticating Users):
    • 通過 Auth Facade (Auth::) 來存取 Laravel 的認證服務
    • attempt(),這個 attempt() 方法通常會用來處理來自網站「登入」表單的驗證
    • 若成功驗證(Auth::attempt($credential)),則應該產生使用者的 token
      • 成功驗證(Auth::attempt($credential) 為 true)後,可透過 Auth::user() 取得使用者資訊
      • 若認證失敗(Auth::attempt($credential) 為 false),可能的原因是註冊時,密碼未經加密,經過 attempt() 得到 false

使用 API/JWT 作為 使用者登入 時,當初註冊必須用加密過的密碼嗎?

如果使用 web/session 方式,可以用密碼為明碼吧?!

<?php
public function login(Request $request)
    {
        $credential = $this->validate($request, [
            'mobile'    => ['required', 'email', 'max:255'],
            'password' => ['required', 'alpha_num', 'min:4', 'max:8'],
        ]);
    
        // 認證成功會給予token、認證不成功會給你false
        $token = Auth::attempt($credential);
        // 取得目前登入的使用者資訊
        $user = Auth::user();
       
        abort_if(!$token, Response::HTTP_BAD_REQUEST, '帳號密碼錯誤');
        return response(['data' => $token, 'user' => $user]);
    }
  • 實際測試,經 web/session 測試後,未加密密碼是無法通過 login() 的 Auth::attempt($credential),也就是不管是 api/jwt 或 web/session 加密後的密碼,才能通過 attempt(),若使用明碼通過 attempt() 則會得到 false
  • 主因是 attempt() 將上述 $credential 陣列中的值,用來在資料庫資料表中尋找使用者。因此,在上方的範例中,使用者會依照 mobile 欄位中的值來取得。若找到該使用者,則會將資料庫中儲存的密碼雜湊跟陣列中的 password 值進行比對
    • 當初註冊時,password 用明碼註冊,也就是說,資料庫中儲存的密碼是明碼,此時登入的 password 經過雜湊後,斷然與 資料庫中儲存的密碼不符合。遂不管 api/jwt 或 web/session ,註冊時,必須將密碼加密後,才能通過 attempt()。
  • call api 結果:

應用 JWT 認證 Logout 為例

Logout 的前提必須是 "登入" 狀態,當使用者在 "登入" 狀態下,會有一組 JWT token,清除使用者的認證狀態(JWT 工具產生的 token),即為使使用者登出

  • 當前使用者通過 middleware('auth:member') 確認是 "登入" 狀態,才能允許他們繼續訪問 Route::post('logout', ..略)
  • 否則,call api 失敗,將收到 401 status code ("message": "Unauthenticated."),需要先登入才能訪問該路由
    • Logout Route 設定:
    • api: 127.0.0.1:8083/v1/members/logout
<?php
Route::middleware(['auth:member'])->group(function () {
    Route::post('/members/logout', [AuthController::class, 'logout']);
});
  • Laravel 框架內建的函式 Auth::logout(),它會清除使用者的認證狀態(JWT 工具產生的 token),促使 使用者登出
  • return response()->noContent() 會回傳一個 HTTP 狀態碼為 204 No Content 的回應給客戶端,代表成功執行但不需要回傳任何資料
    • AuthController logout(),如下:
<?php
 public function logout()
    {
        // 清除使用者的認證狀態(JWT 工具產生的 token)
        Auth::logout();
        return response()->noContent();
    }
  • call api 結果:

結語

call api 的 JWT 類似金手指的概念,它提供 call api 所需的認證,經由設定 config/auth.phpMember model implements JWTSubject ,在 AuthController 內,用 Auth:: 來使用 JWT 的功能,例如:

  • Auth::attempt($credential), Auth::logout(), Auth::user() 等功能

參考文章

1 [Day 27] Call API use JWT in Laravel: 簡介 和 安裝
2 Laravel #Manually Authenticating Users

是誰在敲打我窗?什麼是 JWT ?


上一篇
[Day 27] Call API use JWT in Laravel: 簡介 和 安裝
下一篇
[Day 29] Laravel 怎麼觸發設定在 class ProductResource 的 toArray()?
系列文
PHP 沿途的風景30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言