Otentikasi API Pada Framework Laravel Menggunakan Core JWT - CRUDPRO

Otentikasi API Pada Framework Laravel Menggunakan Core JWT

Pada Artikel ini, saya akan menjelaskan manfaat otentikasi JWT dibandingkan metode tradisional dan menggunakan Laravel dan JWT untuk mengimplementasikan sistem otentikasi sederhana.

Dalam contoh ini, kami menggunakan tymondesigns/jwt-auth, perpustakaan JWT hebat yang dibuat untuk Laravel. Namun, artikel ini kembali ke dasar. Menerapkan otentikasi di Laravel menggunakan paket inti JWT PHP, firebase/php-jwt. Salah satu alasannya adalah untuk memahami pada tingkat rendah bagaimana JWT bekerja dan untuk memahami implementasi yang mungkin Anda temui di masa depan. Alasan lainnya adalah fleksibilitas. Paket inti memberi Anda kebebasan untuk menyesuaikan JWT Anda untuk mendapatkan apa yang Anda butuhkan untuk contoh tertentu.

.

Ayo mulai.

Instal Laravel dan dependensi lainnya

Versi 8 atau lebih tinggi sudah cukup untuk menjalankan project ini (saya menggunakan versi 8 untuk project ini). Untuk menginstal Laravel, Anda dapat menjalankan perintah berikut di folder root server Anda:

composer create-project laravel/laravel:^8.0 laravel-core-jwtcd laravel-core-jwt
cd laravel-core-jwt

Seperti disebutkan di atas, gunakan paket firebase/php-jwt. Anda dapat menggunakan perintah berikut untuk menginstal:

composer require firebase/php-jwt

Catatan: Kode sumber untuk project ini dapat ditemukan di https://github.com/joshuaetim/laravel-core-jwt.

Membuat token generator

Selanjutnya, buat generator token yang akan menjadi inti dari prosedur otentikasi. Ini adalah salah satu penangan yang merupakan cara untuk mengatur metode yang dapat digunakan kembali di controller dan middleware. Kode ditunjukkan di bawah ini.

<?php

namespace App\Handlers\Admin;

use Firebase\JWT\JWT;
use DateTimeImmutable;

class AuthHandler
{
    /**
     * Handles operations related to admin authentication
     */

    // generate token
    public function GenerateToken($user)
    {
        $secretKey  = env('JWT_KEY');
        $tokenId    = base64_encode(random_bytes(16));
        $issuedAt   = new DateTimeImmutable();
        $expire     = $issuedAt->modify('+6 minutes')->getTimestamp();     
        $serverName = "your.server.name";
        $userID   = $user->id;                                    

        // Create the token as an array
        $data = [
            'iat'  => $issuedAt->getTimestamp(),    
            'jti'  => $tokenId,                     
            'iss'  => $serverName,                  
            'nbf'  => $issuedAt->getTimestamp(),    
            'exp'  => $expire,                      
            'data' => [                             
                'userID' => $userID,            
            ]
        ];

    // Encode the array to a JWT string.
        $token = JWT::encode(
            $data,      
            $secretKey, 
            'HS512'     
        );
        return $token;
    }
}

Disimpan di app/Handlers/Admin/AuthHandler.php

Yang penting di sini adalah baris 25, dimana data yang disimpan dalam token didefinisikan, dan baris 37, data di encode dan mereturn JWT ke pemanggil. Dalam hal ini, ini akan menjadi token otentikasi untuk otentikasi user. Metode GenerateToken menerima model user yang akan menjadi user yang baru terdaftar atau masuk. Nilai ini akan segera berlalu.

helper publik

Gunakan helper untuk mendapatkan token dari request header dan gunakan paket JWT untuk memecahkan kodenya. Ini adalah helper publik karena dapat digunakan di bagian mana pun dari aplikasi. Kode ditunjukkan di bawah ini.

<?php

namespace App\Helpers;

use App\Exceptions\AuthenticationException;
use Exception;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use DateTimeImmutable;

class PublicHelper 
{
    // get jwt info from header
    public function GetRawJWT()
    {
        // check if header exists
        if(empty($_SERVER['HTTP_AUTHORIZATION'])) {
            throw new AuthenticationException('authorization header not found');
        }

        // check if bearer token exists
        if (! preg_match('/Bearer\s(\S+)/', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
            throw new AuthenticationException('token not found');
        }

        // extract token
        $jwt = $matches[1];
        if (!$jwt) {
            throw new AuthenticationException('could not extract token');
        }

        return $jwt;
    }

    public function DecodeRawJWT($jwt)
    {
        // use secret key to decode token
        $secretKey  = env('JWT_KEY');
        try {
            $token = JWT::decode($jwt, new Key($secretKey, 'HS512'));
            $now = new DateTimeImmutable();
        } catch(Exception $e) {
            throw new AuthenticationException('unauthorized');
        }

        return $token;
    }

    public function GetAndDecodeJWT()
    {
        $jwt = $this->GetRawJWT();
        $token = $this->DecodeRawJWT($jwt);

        return $token;
    }
}

Disimpan di app/Helpers/PublicHelper.php

Kami telah membagi metode menjadi tiga untuk memfasilitasi useran kembali oleh kelas lain. GetRawJWT mengekstrak token dari header dan DecodeRawJWT menggunakan paket untuk memecahkan kode token. Metode ini dikelompokkan bersama dalam GetAndDecodeJWT untuk mendapatkan apa yang Anda butuhkan dari proses otentikasi.

Variabel environtment

Pada titik ini, Anda seharusnya memperhatikan env('JWT_KEY') yang digunakan dalam paket JWT. Ini biasanya string acak yang harus dirahasiakan. Untuk environtment berbasis UNIX, satu cara yang baik untuk menghasilkan kunci yang aman secara kriptografis adalah dengan menjalankan perintah berikut:

openssl rand -hex 32

Kemudian simpan variabel dalam file .env Anda sebagai berikut:

JWT_KEY="b0100cb96626bd46fdb21ea10650d549c8089b7a33644c52f03c0a79c9ff187d"

Controller

Buat AuthController yang menangani endpoint untuk membuat login untuk users. Tapi pertama-tama, buat base controller untuk memformat respons ke client. Ini disebut controller API.

<?php

namespace App\Http\Controllers;

use App\Handlers\Admin\AuthHandler;
use App\Models\User;
use Firebase\JWT\JWT;
use DateTimeImmutable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\APIController;
use Illuminate\Support\Facades\Validator;

class AuthController extends APIController
{
    // register
    public function register(Request $request)
    {
        $input = $request->only('name', 'email', 'password', 'c_password');

        $validator = Validator::make($input, [
            'name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required|min:8',
            'c_password' => 'required|same:password',
        ]);

        if($validator->fails()){
            return $this->sendError('Validation Error', $validator->errors(), 422);
        }

        $input['password'] = bcrypt($input['password']);
        $user = User::create($input);

        if ($user) {
            $authHandler = new AuthHandler;
            $token = $authHandler->GenerateToken($user);

            $success = [
                'user' => $user,
                'token' => $token,
            ];
    
            return $this->sendResponse($success, 'user registered successfully', 201);
        }
        
    }

    public function login(Request $request)
    {
        $input = $request->only('email', 'password');

        $validator = Validator::make($input, [
            'email' => 'required',
            'password' => 'required',
        ]);

        if($validator->fails()){
            return $this->sendError('Validation Error', $validator->errors(), 422);
        }

        $remember = $request->remember;

        if(Auth::attempt($input, $remember)){
            $user = Auth::user();

            $authHandler = new AuthHandler;
            $token = $authHandler->GenerateToken($user);

            $success = ['user' => $user, 'token' => $token];

            return $this->sendResponse($success, 'Logged In');
        }
        else{
            return $this->sendError('Unauthorized', ['error' => "Invalid Login credentials"], 401);
        }
    }
}

app/Http/Controllers/AuthController.php

Controller ini menggunakan handler yang Anda buat sebelumnya. Metode register menambahkan user baru ke database dan menggunakan metode GenerateToken yang dibuat sebelumnya untuk menghasilkan token. Ini akan dikirim ke user untuk memberikan akses kepada user selama token valid. Metode login memeriksa detail login terhadap database dan menghasilkan token jika kecocokan berhasil.

Middleware

Anda memerlukan middleware untuk memeriksa apakah user yang membuat request diautentikasi. helper yang Anda buat sejauh ini berguna di sini. Gunakan perintah dari artisan Laravel untuk membuat middleware.

php artisan make:middleware JWTVerify

Isi dari JWTVerify adalah sebagai berikut:

<?php

namespace App\Http\Middleware;

use App\Helpers\PublicHelper;
use Closure;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use DateTimeImmutable;
use Exception;
use Firebase\JWT\ExpiredException;
use Illuminate\Http\Request;

class JWTVerify
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $publicHelper = new PublicHelper();

        try {
            $token = $publicHelper->GetAndDecodeJWT();
        } catch (Exception $e) {
            return response()->json(['error' => $e->getMessage()], 401);
        }

        return $next($request);
    }
}
</code></pre>
<p align="center">app/Http/Middleware/JWTVerify.php</p>

<p align="justify">Middleware ini menggunakan kelas <b>PublicHelper</b> untuk mendapatkan dan decode JWT dari request header. Jika ada inkonsistensi seperti token yang expired, token yang salah, atau tidak ada header, paket JWT akan memunculkan exception dan melaporkannya sebagai respon error.</p>

<p align="justify">Untuk menggunakan middleware ini, daftarkan di file <b>Kernel.php</b>:</p>
<pre style="background-color:transparent; border:none;"><code class="php">&lt;?php

use App\Helpers\AdminHelper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\HomeController;

/*
|--------------------------------------------------------------------------
| 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 &quot;api&quot; middleware group. Enjoy building your API!
|
*/

Route::middleware('jwt.auth')-&gt;get('/user', function (Request $request) {
    $adminHelper = new AdminHelper();
    $user = $adminHelper-&gt;GetAuthUser();
    return response()-&gt;json(['data' =&gt; $user], 200);
});


Route::get('/home', [HomeController::class, 'home']);

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login'])-&gt;name('login');

Ada dua kelas baru di sini, AdminHelper dan HomeController. Kelas AdminHelper cukup mendecode token dan mengembalikan user yang masuk. HomeController menyediakan halaman publik untuk ditampilkan kepada user.

<?php

namespace App\Helpers;

use App\Models\User;

class AdminHelper 
{
    // get authenticated user from DB
    public function GetAuthUser()
    {
        $publicHelper = new PublicHelper();
        $token = $publicHelper->GetAndDecodeJWT();

        $userID = $token->data->userID;
        $user = User::find($userID);
        return $user;
    }
}

app/Helpers/AdminHelper.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use App\Http\Controllers\APIController;

class HomeController extends APIController
{
    public function home()
    {
        $data = [
            'page' => "This is the home page"
        ];

        $message = "You found me!";

        return $this->sendResponse($data, $message);
    }
}

app/Http/Controllers/HomeController.php

Final

Hasil akhirnya adalah sistem otentikasi sederhana yang menggunakan JWT secara langsung, memberi Anda lebih banyak fleksibilitas dalam cara Anda menggunakan JWT. Uji dengan Tukang Pos untuk melihat cara kerja kode Anda dan beri tahu kami jika ada masalah atau jika Anda memiliki saran di kotak komentar.

Tangkapan Layar Postman untuk user login

Endpoint untuk get user

Dalam tutorial berikutnya, kami akan membagikan cara membuat user logout dan lebih meningkatkan keamanan.