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

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

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

資料庫

使用瀏覽器開啟 phpMyAdmin,在 MariaDBMySQL 新建一個資料庫名稱 laravel_db,編碼與排序使用 utf8mb4_unicode_ci

新建資料庫 laravel_db
新建資料庫 laravel_db

Laravel 8 資料庫設定

修改檔案 .env 中第 4 行 DB_DATABASE。(資料庫名稱可自訂,只要與 DB_DATABASE 對應即可)

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=xxx
DB_PASSWORD=xxx

自動新增預設資料表:

php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (12.28ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (13.91ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (16.50ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (28.56ms)

這時查看資料庫會發現多了 5 個資料表 failed_jobsmigrationspassword_resetspersonal_access_tokensusers

php artisan migrate 指令自動產生的 5 個資料表
php artisan migrate 指令自動產生的 5 個資料表

JWT

安裝 JWT (JSON Web 令牌) 套件。

composer require tymon/jwt-auth
# 以上省略 ...

> @php artisan vendor:publish --tag=laravel-assets --ansi
No publishable resources for tag [laravel-assets].
Publishing complete.

config/app.php 的 Laravel service providers 和 aliases 各自 include Tymon\JWTAuthJWTAuthJWTFactory 至結尾:

<?php

// 以上省略

'providers' => [
    // 以上省略 ....
    Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
],
'aliases' => [
    // 以上省略 ....
    'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
    'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
],

產生 config/jwt.php 設定檔:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
# 以上省略 ...

Copied File [/vendor/tymon/jwt-auth/config/config.php] To [/config/jwt.php]
Publishing complete.

產生 secret key (JWT 密鑰):

php artisan jwt:secret
jwt-auth secret [whyGL2aPpZlYPiyBTvWDT0hWNq9Lk5OG2V5J979qru8ICfg0DYMsR98ALoKNb1Kk] set successfully.

成功後會將產生的 secret key 存放在檔案 .env 結尾中:

JWT_SECRET=whyGL2aPpZlYPiyBTvWDT0hWNq9Lk5OG2V5J979qru8ICfg0DYMsR98ALoKNb1Kk

設定

修改 app/Models/User.php

  1. use Tymon\JWTAuth\Contracts\JWTSubject;
  2. class 宣告要 implements JWTSubject
  3. 結尾加入兩個 function,getJWTIdentifier()getJWTCustomClaims()
<?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 Laravel\Sanctum\HasApiTokens;

use Tymon\JWTAuth\Contracts\JWTSubject;

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

    /**
     * The attributes that are mass assignable.
     *
     * @var string[]
     */
    protected $fillable = [
        # 可自行加上自訂欄位,例如權限 (DB table 要自行新增)
        # 'role',
        'name',
        'email',
        'password',
    ];

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

    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    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 [];
    }
}

修改 config/auth.php

  • 17 行:原 web 改為 api。
  • 44~48 行:新增的程式。
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
            'hash' => false,
        ],        
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | times out and the user is prompted to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => 10800,

];

建立 Controller

建立認證的 Authentication Controller:

php artisan make:controller AuthController
Controller created successfully.

將下列所有程式碼複製貼上 app/Http/Controllers/AuthController.php

<?php

namespace App\Http\Controllers;

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

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login', 'register']]);
    }
    
    /**
     * Get a JWT via given credentials. (使用者登入)
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login(Request $request)
    {
        $validator = Validator::make($request->all(), [
            # 'role' => 'required|string',
            'email' => 'required|email',
            'password' => 'required|string|min:6',
        ]);
        if ($validator->fails()) {
            return response()->json($validator->errors(), 422);
        }
        if (!$token = auth()->attempt($validator->validated())) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
        return $this->createNewToken($token);
    }

    /**
     * Register a User. (使用者註冊)
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            # 'role' => 'required|string',
            'name' => 'required|string|between:2,100',
            'email' => 'required|string|email|max:100|unique:users',
            'password' => 'required|string|confirmed|min:6',
        ]);
        if ($validator->fails()) {
            return response()->json($validator->errors()->toJson(), 400);
        }
        $user = User::create(array_merge(
            $validator->validated(),
            ['password' => bcrypt($request->password)]
        ));
        return response()->json([
            'message' => 'User successfully registered',
            'user' => $user
        ], 201);
    }

    /**
     * Log the user out (Invalidate the token). (使用者登出,移除 JWT token)
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        auth()->logout();
        return response()->json(['message' => 'User successfully signed out']);
    }

    /**
     * Refresh a token. (更新 JWT token)
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->createNewToken(auth()->refresh());
    }

    /**
     * Get the authenticated User. (以 JWT token 取得使用者資訊)
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function userProfile()
    {
        return response()->json(auth()->user());
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function createNewToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth()->factory()->getTTL() * 60,
            'user' => auth()->user()
        ]);
    }
}

路由

設定 routes/api.php,第 6 行、24 ~ 33 行為新增的程式碼:

<?php

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

use App\Http\Controllers\AuthController;

/*
|--------------------------------------------------------------------------
| 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!
|
*/

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

Route::group([
    'middleware' => 'api',
    'prefix' => 'auth'
], function ($router) {
    Route::post('/register',    [AuthController::class, 'register']);       // 使用者註冊
    Route::post('/login',       [AuthController::class, 'login']);          // 使用者登入 (回傳 JWT token 及使用者資訊)
    Route::get('/user-profile', [AuthController::class, 'userProfile']);    // 以 JWT token 取得使用者資訊
    Route::post('/refresh',     [AuthController::class, 'refresh']);        // 更新 JWT token
    Route::post('/logout',      [AuthController::class, 'logout']);         // 使用者登出,移除 JWT token
});

測試

先到 Download Postman | Get Started for Free 下載並安裝可以發送和接收 HTTP 請求的軟體。

使用者註冊

使用者註冊網址 https://laravel8-jwt.footmark.com.tw/api/auth/register,使用 POST 在 Body 傳送給伺服器的資訊。

{
    "name": "footmark",
    "email": "footmark.info@gmail.com",
    "password": "abc123",
    "password_confirmation": "abc123"
}

Postman POST 使用者註冊,回傳註冊成功資訊:

Postman POST 使用者註冊,回傳註冊成功資訊
Postman POST 使用者註冊,回傳註冊成功資訊

使用者登入

使用者註冊登入網址 https://laravel8-jwt.footmark.com.tw/api/auth/login,使用 POST 在 Body 傳送給伺服器的資訊。

{
    "email": "footmark.info@gmail.com",
    "password": "abc123"
}

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

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

以 JWT token 使用 API 取得使用者資訊

以 JWT token 使用 API 取得使用者資訊網址 https://laravel8-jwt.footmark.com.tw/api/auth/user-profile,使用 GET 而 Auth 認證類型須使用 Bearer Token,並將上述取得的 Token 複製並貼至右則的 Token 欄位。

如果 JWT Token 認證沒有問題,則 API 就會回傳使用者資訊。

使用者更新 JWT token

更新 JWT token 網址 https://laravel8-jwt.footmark.com.tw/api/auth/refresh,使用 POST 而 Auth 認證類型須使用 Bearer Token,並將上述取得的 Token 複製並貼至右則綠框的 Token 欄位。

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

使用者登出,移除 JWT token

使用者登出,移除 JWT token 網址 https://laravel8-jwt.footmark.com.tw/api/auth/logout,使用 POST 而 Auth 認證類型須使用 Bearer Token,並將上述取得的 Token 複製並貼至右則的 Token 欄位。

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

參考

發表留言