การทำ Authentication ใน Lumen + JWT

Posted by Mathiphic on April 24,2023

รู้จักกับ Lumen คืออะไร
Lumen คือ PHP Framework ที่ถูกพัฒนามาจาก Laravel โดยทำให้ Laravel Framework นั้นมีขนาดเล็กลงโดยการตัด Package บางตัวที่เกินความจำเป็นออก

 

Project Requirements

  1. PHP >= 7
  2. MySQL
  3. PDO PHP Extension
  4. Mbstring PHP Extension
  5. Composer >2.0.


ติดตั้ง LUMEN

ติดตั้งผ่าน Composer โดยใช้คำสั่ง

composer create-project --prefer-dist laravel/lumen lumen-jwt-auth


ในที่นี้จะตั้งชื่อ Project ว่า lumen-jwt-auth

เมื่อติดตั้งเสร็จแล้วทำการ Start server เพื่อดูหน้าตาของโปรเจ็คด้วยคำสั่งดังนี้

// เข้าไปที่ Folder project ที่สร้างขึ้น
cd lumen-jwt-auth
php -S localhost:8000 -t public

CONFIG PROJECT

1. GENERATE KEY

วิธีสร้าง Key นั้นมีหลายแบบ ในที่นี้จะใช้ \Illuminate\Support\Str::random(32);
โดยเข้าไปที่ ./routes/web.php แล้วเพิ่ม code

$router->get('/key', function () {
    return response()->json([
        'APP_KEY' => \Illuminate\Support\Str::random(32),
        'JWT_SECRET' => \Illuminate\Support\Str::random(32)
    ]);
});

 

Start server แล้วไปที่ URL http://localhost:8000/key จะได้ข้อมูลดังนี้

{
   "APP_KEY": "nqdMSGuZZGp7agnJpZE1jQaNhBDyvKE0",
   "JWT_SECRET": "HRzDYZGQ04JEJfgMWy9UqnhfVyM2jqAMfneD04hffiMwE1hu7Nf5JPLJc1L4B0CD"
}

 

นำผลลัพธ์ที่ได้ไปแก้ไขที่ .env และเพิ่มค่า JWT_EXPIRE_HOUR (วันหมดอายุ Token หน่วยเป็น hour)

APP_KEY=qSPgQnFU50yfgBHNxLKz54t307nQ6z2x

    . . . 

JWT_SECRET=qSPgQnFU50yfgBHNxLKz54t307nQ6z2x
JWT_EXPIRE_HOUR=24

2. สร้างฐานข้อมูล

ให้ทำการสร้าง Database ใน MySQL แล้ว Config เพื่อเชื่อมต่อ

DB_PORT={DB_port}
DB_DATABASE={DB_name}
DB_USERNAME={DB_username}
DB_PASSWORD={DB_password}

 

3. ทำการสร้าง MIGRATION

php artisan make:migration create_users_table

แก้ไขไฟล์ .database/migrations/{timestamp}_create_users_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email', 100)->unique();
            $table->string('password');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
}

 

จากนั้น run คำสั่ง migration ใน cmd เพื่อสร้าง table ในฐานข้อมูล

php artisan migrate


แก้ไขไฟล์ .bootstrap/app.php

<?php
. . . 
$app->withFacades();  // uncomment
$app->withEloquent();  // uncomment

 

4. สร้าง MODEL เพื่อติดต่อกับฐานข้อมูล

โดยสร้าง ไฟล์ที่ .app/Models/Users.php และเพิ่ม code ดังนี้

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

    class Users extends Model
    {
        protected $table = 'users';
        protected $fillable = [
            'id', 'name', 'email',
        ];

    protected $hidden = ['password'];
}

 

5. ติดตั้ง INSTALL JWT LIBRARY

Docs: https://github.com/firebase/php-jwt

โดยจะติดตั้ง firebase/php-jwt โดย run คำสั่งต่อไปนี้ ใน cmd

composer require firebase/php-jwt

 

6. สร้าง CONTROLLER

โดยสร้าง ไฟล์ที่ .app/Http/Controller/AuthController.php และเพิ่ม code ดังนี้

<?php

namespace App\Http\Controllers;

use App\Models\Users;
use Firebase\JWT\JWT;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class AuthController extends Controller
{

    public function __construct(){}

    /*
    |--------------------------------------------------------------------------
    | Api สมัครสมาชิก
    |--------------------------------------------------------------------------
     */
    public function register(Request $request)
    {
        // validator
        $validator = Validator::make($request->all(), [
            'email' => 'required|email|unique:users',
            'username' => 'required|string|unique:users',
            'password' => 'required',
            'name' => 'required|string',
        ]);

        if ($validator->fails()) {
            $errors = $validator->errors();

            return $this->responseRequestError($errors);
        } else {
            $user = new Users();
            $user->email = $request->email;
            $user->name = $request->name;
            $user->username = $request->username;
            $user->password = Hash::make($request->password);
            if ($user->save()) {
                $token = $this->jwt($user);
                $user['api_token'] = $token;
                return $this->responseRequestSuccess($user);
            } else {
                return $this->responseRequestError('Cannot Register');
            }
        }
    }

    /*
    |--------------------------------------------------------------------------
    | Api เข้าสู่ระบบ
    |--------------------------------------------------------------------------
     */
    public function login(Request $request)
    {

        $user = Users::where('username', $request->username)
            ->first();

        if (!empty($user) && Hash::check($request->password, $user->password)) {
            $token = $this->jwt($user);
            $user["api_token"] = $token;

            return $this->responseRequestSuccess($user);
        } else {
            return $this->responseRequestError("Username or password is incorrect");
        }
    }

    /*
    |--------------------------------------------------------------------------
    | ตัวเข้ารหัส JWT
    |--------------------------------------------------------------------------
     */
    protected function jwt($user)
    {
        $payload = [
            'iss' => "lumen-jwt", // Issuer of the token
            'sub' => $user->id, // Subject of the token
            'iat' => time(), // Time when JWT was issued.
            'exp' => time() + env('JWT_EXPIRE_HOUR') * 60 * 60, // Expiration time
        ];

        return JWT::encode($payload, env('JWT_SECRET'), 'HS256');
    }

    /*
    |--------------------------------------------------------------------------
    | response เมื่อข้อมูลส่งถูกต้อง
    |--------------------------------------------------------------------------
     */
    protected function responseRequestSuccess($ret)
    {
        return response()->json(['status' => 'success', 'data' => $ret], 200)
            ->header('Access-Control-Allow-Origin', '*')
            ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    }

    /*
    |--------------------------------------------------------------------------
    | response เมื่อข้อมูลมีการผิดพลาด
    |--------------------------------------------------------------------------
     */
    protected function responseRequestError($message = 'Bad request', $statusCode = 200)
    {
        return response()->json(['status' => 'error', 'error' => $message], $statusCode)
            ->header('Access-Control-Allow-Origin', '*')
            ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    }

}

 

สิ่งที่เรานำมาใช้ใน Project

use Firebase\JWT\JWT; // เรียกใช้ JWT เพื่อนำมาสร้าง Token
use App\Models\Users; // เรียกใช้ Models เพื่อติดต่อกับ Table Users ในฐานข้อมูล
use Illuminate\Http\Request; // ใช้รับค่าที่ส่งมา
use Illuminate\Support\Facades\Hash; // ใช้เข้ารหัสข้อมูล เช่น password
use Illuminate\Support\Facades\Validator; // ใช้ตรวจสอบความถูกต้องและครบถ้วนของข้อมูล

 

7. สร้าง Route เพื่อเรียกใช้ Class ใน Controller
เพิ่ม Code ดังนี้

$router->group(['prefix' => 'api/auth'], function ($router) {

    // URL: http://localhost:8000/api/auth/register, method: Post
    $router->post('register', 'AuthController@register');

    // URL: http://localhost:8000/api/auth/login, method: Post
    $router->post('login', ['uses' => 'AuthController@login']); 

});


7.1 ใช้ Postman ทดสอบที่ URL http://localhost:8000/api/auth/register ดังรูป

 

7.2 ใช้ Postman ทดสอบที่ URL http://localhost:8000/api/auth/login ดังรูป

 

8. สร้าง MIDDLEWARE เพื่อใช้ป้องกัน ROUTE

สร้าง file .app/Http/Middleware/JwtMiddleware.php และเพิ่ม code ดังนี้

<?php
namespace App\Http\Middleware;

use App\Models\Users;
use Closure;
use Exception;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class JwtMiddleware
{
    public function handle($request, Closure $next, $guard = null)
    {

      $token = $request->header('Authorization');

      
        if (!$token) {
            // Unauthorized response if token not there
            return response()->json([
                'error' => 'Token not provided.',
            ], 401);
        }
        try {
            $credentials = JWT::decode($token, new Key(env('JWT_SECRET'), 'HS256'));
        } catch (ExpiredException $e) {
            return response()->json([
                'error' => 'Provided token is expired.',
            ], 400);
        } catch (Exception $e) {
            return response()->json([
                'error' => 'An error while decoding token.',
            ], 400);
        }
        $user = Users::find($credentials->sub);
        // Now let's put the user in the request class so that you can grab it from there
        $request->auth = $user;
        return $next($request);
    }
}

 

9. เปิดใช้งาน MIDDLEWARE

ที่ .bootstrap/app.php โดยการเพิ่ม code

$app->routeMiddleware([
    'jwt.auth' => App\Http\Middleware\JwtMiddleware::class,
]);

 

10. เพิ่ม MIDDLEWARE ใน ROUTE

ทำการเพิ่ม Middleware ใน URL ที่เราต้องการให้ login ก่อนเข้าใช้งาน

$router->group(['prefix' => 'api/', 'middleware' => 'jwt.auth'], function ($router) {

    // URL: http://localhost:8000/api/'test', method: get
    $router->get('test', function () {
        return 'Hi admin';
    });

}); 

 

ทำการทดสอบโดยใช้ Postman ดังภาพ โดยเพิ่ม Header ชื่อ Authorization โดยที่ value คือ token ที่ return กลับมาในข้อที่ 7.2

 

เพียงเท่านี้ก็เป็นอันเสร็จสิ้น

************************************************ จบแล้ว ************************************************


แนะนำ

บทความใหม่

ภาษา JavaScript มีการทำงานแบบ Asynchronous คือเวลาที่เราสั่งงานอะไรไป ถ้าเป็นงานที่ใช้เวลานาน ก็จะทำคำสั่งถัดไปเลยโดยไม่รอให้คำสั่งก่อนหน้าทำเสร็จ ซึ่งก่อให้เกิดปัญหาในหลายกรณี

PHP Framework ที่ถูกพัฒนามาจาก Laravel โดยทำให้ Laravel Framework นั้นมีขนาดเล็กลงโดยการตัด Package บางตัวที่เกินความจำเป็นออก

 Library ตัวหนึ่งสำหรับการสร้างหน้าต่างแจ้งเตือน บนเว็บไซต์ของเราได้อย่างง่ายดาย สวยงาม และปรับแต่งได้ รวมไปถึง Responsive และ UX/UI อีกด้วย