Laravel 9 JSON Web Token (JWT) 身份驗證教學範例 for Web RESTful API

Laravel

Laravel 9 搭配 php-open-source-saver/jwt-auth 套件實作完整的 RESTful Web API 登入認證令牌 JSON Web Token (JWT),包含使用者註冊、登入、以 JWT 認證使用 API、更新 JWT 及使用者登出時移除 JWT。

資料庫設定

請參考 Laravel 8 RESTful Web API 登入認證令牌 JSON Web Token (JWT)

安裝並設定 JWT

上述已遷移完資料庫資料表 users,接著安裝並設定 Laravel JWT 身份驗證套件,使用以下指令安裝:

composer require php-open-source-saver/jwt-auth

使用以下指令將 JWT 設定文件從供應商複製到 confi/jwt.php

php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"

產成 JWT 令牌加密 (secret):

php artisan jwt:secret

JWT 加密產生在 .env

JWT_SECRET = xxxxxxxx

設定 AuthGuard

需要對 Laravel 進行一些設定,才能使 JWT AuthGuard 支援應用程身份驗證。對此檔案進行修改 config/auth.php (請保留原程式):

  • 使用 API guard 使用 JWT driver 並將 API 設置為 guard 默認 API。
  • 現在可以使用 Laravel 的內建身份驗證機制來工作 jwt-auth
<?php
'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],


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

        'api' => [
                'driver' => 'jwt',
                'provider' => 'users',
        ],
    ],

修改 User 模型

為了使 PHPOpenSourceSaverJWTAuthContractsJWTSubject 在的 User 模型上實現,將使用兩種 Method:getJWTCustomClaims() and getJWTIdentifier()

將檔案中的程式替換為以下內容 app/Models/User.php

<?php
namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

     /**
     * 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 [];
    }
}

建立 AuthController

新建一個控制器 (Controller) 來處理身份驗證過程的核心邏輯,指行指令來產生:

php artisan make:controller AuthController

將檔案中的程式替換為以下內容 app/Http/Controllers/AuthController.php

  • constructor:我們在類中建立此函數,controller 以便我們可以使用其中的中間件來阻止對控制器中某些方法的未經身份驗證的訪問 auth:api
  • login:此方法使用電子郵件和密碼對用戶進行身份驗證。當用戶成功通過身份驗證時,Auth facade方法將返回 JWT 令牌。檢索生成的令牌並將其作為 JSON 與用戶對像一起返回 attempt()
  • register:此方法創建用戶記錄並使用令牌生成登錄用戶 Auth token。
  • logout:該方法使用戶Authtoken失效。
  • refresh:該方法使用戶 Auth token 失效並生成新的 token。
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

class AuthController extends Controller
{

    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login','register']]);
    }

    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string',
        ]);
        $credentials = $request->only('email', 'password');

        $token = Auth::attempt($credentials);
        if (!$token) {
            return response()->json([
                'status' => 'error',
                'message' => 'Unauthorized',
            ], 401);
        }

        $user = Auth::user();
        return response()->json([
                'status' => 'success',
                'user' => $user,
                'authorisation' => [
                    'token' => $token,
                    'type' => 'bearer',
                ]
            ]);

    }

    public function register(Request $request){
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6',
        ]);

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        $token = Auth::login($user);
        return response()->json([
            'status' => 'success',
            'message' => 'User created successfully',
            'user' => $user,
            'authorisation' => [
                'token' => $token,
                'type' => 'bearer',
                'expires_in' => auth()->factory()->getTTL() * 60    // JWT 有效時間/分鐘
            ]
        ]);
    }

    public function logout()
    {
        Auth::logout();
        return response()->json([
            'status' => 'success',
            'message' => 'Successfully logged out',
        ]);
    }

    public function refresh()
    {
        return response()->json([
            'status' => 'success',
            'user' => Auth::user(),
            'authorisation' => [
                'token' => Auth::refresh(),
                'type' => 'bearer',
            ]
        ]);
    }
}

已經完成了 JWT 身份驗證的設定了。

新增測試 Database API

新增 Database table

新增 EmployeeController:

php artisan make:controller Api/EmployeeController --api

程式碼如下:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class EmployeeController extends Controller
{
    /**
     * 要先通過 JWT 認證才可使用 API
     */
    public function __construct()
    {
        $this->middleware('auth:api');
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $query = DB::connection('sms')
            ->table('employee')
            ->get();

        return [
            'data' => $query
        ];
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $id = DB::connection('sms')
            ->table('employee')
            ->insertGetId($request->all());

        return $this->show($id);
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $query = DB::connection('sms')
            ->table('employee')
            ->where('id', $id)
            ->get();

        return [
            'data' => $query
        ];
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        // $query = DB::connection('sms')
        //     ->table('employee')
        //     ->where('id', $id)
        //     ->update($request->all());
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        DB::connection('sms')
            ->table('employee')
            ->where('id', $id)
            ->delete();
    }
}

API routes

修改 API routes routes/api.php

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

use App\Http\Controllers\AuthController;
use App\Http\Controllers\Api\EmployeeController;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

// Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
//     return $request->user();
// });

Route::controller(AuthController::class)->group(function () {
    Route::post('login', 'login');          // 登入 API
    Route::post('register', 'register');    // 註冊 (取得 JWT)
    Route::post('logout', 'logout');        // 登出
    Route::post('refresh', 'refresh');      // 重新生成 JWT
});

Route::apiResource('employee', EmployeeController::class);

測試 JWT 應用程式

使用者註冊

使用者註冊,使用 POST 在 Body 傳送給伺服器的資訊。

  • name。
  • email。
  • password
Postman POST 使用者註冊,回傳註冊成功資訊及 Token
Postman POST 使用者註冊,回傳註冊成功資訊及 Token

使用者登入

使用者登入,使用 POST 在 Body 傳送給伺服器的資訊。

Postman POST 使用者登入,回傳 JWT token 如下紅框,後續要請求的 API 都得發送這個 token,來讓 Laravel 確認您有權限能使用 API。

Postman POST 使用者登入,回傳 JWT token
Postman POST 使用者登入,回傳 JWT token

取得 API 資訊

未使用 JWT token

Postman 未使用 JWT token 取得 API 資訊失敗
Postman 未使用 JWT token 取得 API 資訊失敗

使用 JWT token

Postman 使用 JWT token 取得 API 資訊成功
Postman 使用 JWT token 取得 API 資訊成功

使用者重新生成 JWT token

更新 JWT token,使用 POST 而 Auth 認證類型須使用 Bearer Token,並將上述取得的 Token 複製並貼至右則綠框的 Token 欄位。

紅框為 API 回傳的新 Token,後續 API 認證就要使用這個新 Token 了。

Postman 使用者重新生成 JWT token
Postman 使用者重新生成 JWT token

使用者登出,移除 JWT token

使用者登出,移除 JWT token,使用 POST 而 Auth 認證類型須使用 Bearer Token,並將上述取得的 Token 複製並貼至右則的 Token 欄位。

顯示使用者已登出,因此 Tokin 已刪除無法使用了。

Postman 使用者登出,移除 JWT token
Postman 使用者登出,移除 JWT token

參考

發表留言