Membuat Rest API merupakan hal umum yang harus dikuasai oleh seorang Backend Engineer, API sendiri digunakan untuk berkomunikasi atau bertukar data dari satu aplikasi ke aplikasi lain. Ada banyak bahasa pemograman yang biasa digunakan oleh programmer untuk membuat API, misalkan Go, JavaScript, Python dan tentu saja PHP. Di tutorial kali ini Saya mau share bagaimana membuat API sederhana (Login, Register, Logout) dengan framework Laravel 9 dan juga implementasi JWT untuk autentikasinya.
Goal dari artikel ini adalah kalian paham membuat API, installasi package php-jwt, dan cara menggunakannya.
Persiapan
Untuk mengikuti tutorial ini pastikan pada komputer kalian terinstall
- PHP 8
- XAMPP / Nginx
- MySQL
- Composer
- Visual Code atau Sublime (bebas)
- Insomnia atau Postman
Pengertian Singkat JWT
Autentikasi adalah proses identifikasi pengguna, merupakan hal krusial dalam sebuah aplikasi, dengan autentikasi aplikasi dapat memastikan apakah user dapat melakukan suatu tugas tertentu atau tidak. Tanpa autentikasi keamanan data tidak dapat terjamin, bisa dibayangkan seandainya ada data penting bisa diakses oleh semua orang. Dalam konteks rest API salah satu cara autentifikasi adalah menggunakan token, library yang banyak digunakan adalah JWT (JSON Web Token). JWT membuat token dari string random yang terdiri dari 3 bagian, nantinya token ini yang akan kita gunakan untuk autentikasi.
Contoh tokennya adalah seperti berikut:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJuYW1lIjoiTmlja3kiLCJhZ2UiOjE5fQ. iMA4D79Sfz0gWzCf70w7oZ9sZmyRkcaGE43IeFGni9A
Bila kita perhatikan terdapat titik sebagai pemisah pada sebuah token, bagian pertama adalah header, yang kedua adalah payload, yang ketika adalah signature
Header : Menyimpan algoritma yang digunakan dalam token tersebut
Payload : Menyimpan data apa saja yang merupakan inti dari token ini, contoh data yang umum disimpan kedalam payload adalah username, email
Signature : Frasa yang digunakan untuk memberitahukan server bahwa token ini benar berasal dan dibuat oleh server tersebut.
Untuk menggunakan token JWT pada rest API dapat ditambahkan pada request header berupa Authorization : Bearer token
Install Laravel 9
Langkah pertama tentu saja menginstall laravel di device kita, disini Saya akan menggunakan composer untuk menginstall laravel dan sebagai contoh Saya akan membuat project Contact
1 2 |
composer create-project laravel/laravel contact cd contact |
Konfigurasi Database
Buatlah database dengan phpmyadmin atau database tool lainnya, beri nama database contact
Edit juga file .env
, sesuaikan dengan settingan MySQL di device kalian
1 2 3 4 5 6 |
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=contact DB_USERNAME=root DB_PASSWORD= |
Secara default Laravel sudah membuat file migration yang berisi beberapa table salah satunya table user, kita tinggal lakukan migrasi table user saja, nantinya secara otomatis akan membuat table user di database kita.
1 |
php artisan migrate |
Installasi JWT
Setelah database sudah tersetup, sekarang kita install package Laravel JWT dari php-open-source-saver/jwt-auth
Jalankan perintah berikut
1 |
composer require php-open-source-saver/jwt-auth |
Setelah selasai langkah selanjutnya adalah mencopy file configurasi jwt ke folder config/jwt.php dengan perintah ini:
1 |
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider" |
Kemudian buat secret key yang akan digunakan untuk menghandle enkripsi token.
1 |
php artisan jwt:secret |
Konfigurasi Guard
Pada file config/auth.php , perlu disesuaiakan supaya si Laravel bisa menggunakan JWT sebagai default authentifikasinya.
Ubah dari
1 2 3 4 |
'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], |
Menjadi
1 2 3 4 |
'defaults' => [ 'guard' => 'api', 'passwords' => 'users', ], |
Kemudian pada Authentication Guards tambahkan
1 2 3 4 5 6 7 8 9 10 |
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ], |
Modifikasi Model User
Kita perlu mengimplementasikan PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject ke dalam model User
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
<?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 PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject; use Illuminate\Support\Facades\Hash; class User extends Authenticatable implements JWTSubject { use HasApiTokens, HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array<int, string> */ protected $fillable = [ 'name', 'email', 'username', '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 []; } } |
Menambahkan Middleware
Tak ketinggalan kita juga perlu menambahkan middleware yang digunakan sebagai penengah sebelum request kita masuk ke dalam controller. Di dalam middleware yang akan kita buat berisikan konfigurasi request header dan pengecekan token JWT
buat middleware dengan command berikut
1 |
php artisan make:middleware JwtMiddleware |
Laravel akan mengenerate file middleware yang berada di app\Http\Middleware
Edit file JwtMiddleware.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Http\Response; use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth; use PHPOpenSourceSaver\JWTAuth\Http\Middleware\BaseMiddleware; class JwtMiddleware extends BaseMiddleware { /** * Handle an incoming request using JWT Token * * @param \Illuminate\Http\Request $request * @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse */ public function handle(Request $request, Closure $next) { $request->headers->set("Content-Type", "application/json"); $request->headers->set("Accept", "application/json"); try { $user = JWTAuth::parseToken()->authenticate(); } catch (\Exception $e) { if ($e instanceof \PHPOpenSourceSaver\JWTAuth\Exceptions\TokenInvalidException) { return response()->json(['status' => 'Error', 'message' => 'Token is Invalid'], Response::HTTP_UNAUTHORIZED); } else if ($e instanceof \PHPOpenSourceSaver\JWTAuth\Exceptions\TokenExpiredException) { return response()->json(['status' => 'Error', 'message' => 'Token is Expired'], Response::HTTP_UNAUTHORIZED); } else { return response()->json(['status' => 'Error', 'message' => 'Authorization Token not found'], Response::HTTP_UNAUTHORIZED); } } return $next($request); } } |
Supaya middleware ini dapat digunakan, tentu saja harus didaftar dulu di app\Http\Kernel.php pada
1 |
'jwt.verify' => \App\Http\Middleware\JwtMiddleware::class |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
<?php namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { /** * The application's global HTTP middleware stack. * * These middleware are run during every request to your application. * * @var array<int, class-string|string> */ protected $middleware = [ // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Illuminate\Http\Middleware\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ]; /** * The application's route middleware groups. * * @var array<string, array<int, class-string|string>> */ protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; /** * The application's route middleware. * * These middleware may be assigned to groups or used individually. * * @var array<string, class-string|string> */ protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \App\Http\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'jwt.verify' => \App\Http\Middleware\JwtMiddleware::class ]; } |
Membuat AuthController
Nah sekarang masuk ke inti tutorial, buat Controller yang digunakan untuk register, login, dan logout.
1 |
php artisan make:controller AuthController |
Kemudian sesuaikan kode-nya seperti berikut
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
<?php namespace App\Http\Controllers; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; class AuthController extends Controller { /** * Handle user register * * @param Request $request * @return void|mixed */ public function register(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6', ]); if ($validator->fails()) { return response()->json([ 'error' => $validator->errors() ], Response::HTTP_UNPROCESSABLE_ENTITY); } $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]); $credentials = $request->only(['email', 'password']); $token = auth()->attempt($credentials); return response()->json([ 'status' => 'success', 'message' => 'Success register', 'data' => [ 'user' => $user, 'access_token' => $token, "token_type" => "bearer", ] ], Response::HTTP_OK); } /** * Handle user login * * @param Request $request * @return void|mixed */ public function login(Request $request) { $credentials = $request->only('email', 'password'); $validator = Validator::make($credentials, [ 'email' => 'required|email', 'password' => 'required|string' ]); if ($validator->fails()) { return response()->json([ 'error' => $validator->errors() ], Response::HTTP_UNPROCESSABLE_ENTITY); } if (!$token = auth()->attempt($credentials)) { return $this->errorResponse("Unauthorized", Response::HTTP_UNAUTHORIZED); } $user = auth()->user(); return response()->json([ 'status' => 'success', 'message' => 'Success Login', 'data' => [ 'user' => $user, 'access_token' => $token, "token_type" => "bearer", ] ], Response::HTTP_OK); } /** * Invalidate token * * @return void */ public function logout() { auth()->logout(); return response()->json([ 'status' => 'success', 'message' => 'Success Logout', ], Response::HTTP_OK); } } |
Setup routes
Karena ini API file routes yang harus diedit adalah routes/api.php
Tambahkan paling atas
1 |
use App\Http\Controllers\AuthController; |
1 2 3 4 5 |
Route::controller(AuthController::class)->group(function () { Route::post('register', [AuthController::class, 'register'])->name("auth.register"); Route::post('login', [AuthController::class, 'login'])->name("auth.login"); Route::post('logout', [AuthController::class, 'logout'])->name("auth.logout")->middleware('jwt.verify'); }); |
Uji Coba
Untuk mencoba, jalankan perintah:
1 |
php artisan serve |
Buka postman/insomnia, arahkan urlnya ke http://localhost:8000/api
Uji Register
Uji Login
Uji Logout
Implementasi Dengan Controller
Supaya bisa mencoba dengan ada contohnya, Saya tambahkan sebuah endpoint baru bernama contact, endpoint tersebut hanya bisa diakses dengan menggunakan token.
Buat Model, Migration dan Controller
Jalankan perintah untuk membuat model, migration dan controller sekaligus
php artisan make:model Todo -mc
Edit file migration yang berada di database/migrations/…create_contacts_table
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('contacts', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('phone'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('contacts'); } }; |
Sesuaikan juga modelnya
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Contact extends Model { use HasFactory; protected $fillable = ['name', 'phone']; } |
Jalankan migrate
1 |
php artisan migrate |
Edit file controller ContactController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
<?php namespace App\Http\Controllers; use App\Models\Contact; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Validator; class ContactController extends Controller { /** * Listing all contact * * @return void */ public function index() { $contacts = Contact::all(); return response()->json([ 'status' => 'success', 'message' => 'Success get data', 'data' => [ 'contacts' => $contacts ] ], Response::HTTP_OK); } /** * Store new contact * * @param Request $request * @return void */ public function store(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'phone' => 'required|string|max:20' ]); if ($validator->fails()) { return response()->json([ 'error' => $validator->errors() ], Response::HTTP_UNPROCESSABLE_ENTITY); } $contact = Contact::create([ 'name' => $request->name, 'phone' => $request->phone ]); return response()->json([ 'status' => 'success', 'message' => 'Success create data', 'data' => [ 'contact' => $contact ] ], Response::HTTP_OK); } /** * Update existing data * * @param Request $request * @param int $id * @return void */ public function update(Request $request, $id) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'phone' => 'required|string|max:20' ]); if ($validator->fails()) { return response()->json([ 'error' => $validator->errors() ], Response::HTTP_UNPROCESSABLE_ENTITY); } $contact = Contact::find($id); $contact->name = $request->name; $contact->phone = $request->phone; $contact->save(); return response()->json([ 'status' => 'success', 'message' => 'Success update data', 'data' => [ 'contact' => $contact ] ], Response::HTTP_OK); } /** * Find single data * * @param [type] $id * @return void */ public function show($id) { $contact = Contact::find($id); if (!$contact) { return response()->json([ 'error' => "No Data with ID {$id}" ], Response::HTTP_NOT_FOUND); } return response()->json([ 'status' => 'success', 'message' => 'Success get data', 'data' => [ 'contact' => $contact ] ], Response::HTTP_OK); } /** * Remove data * * @param [type] $id * @return void */ public function destroy($id) { $contact = Contact::find($id); if (!$contact) { return response()->json([ 'error' => "No Data with ID {$id}" ], Response::HTTP_NOT_FOUND); } $contact->delete(); return response()->json([ 'status' => 'success', 'message' => 'Success delete data', ], Response::HTTP_NO_CONTENT); } } |
Tambahkan ke dalam routes
Nah untuk menambahkan pengecekan, apakah request yang dijalankan sudah ada tokennya atau belum, kita panggil middleware yang telah dibuat tadi dengan function middleware(‘namamiddleware’) kita dapat menambahkannya pada routes
1 |
Route::resource('contact', ContactController::class)->middleware('jwt.verify'); |
Uji Coba
Sebelum melakukan uji coba, jalankan login dahulu untuk get token, lalu gunakan token tersebut dalam setiap request, kalau di insomnia pada tab kedua ada Tab Auth, pilih bearer, isikan token yang didapat.
Create Contact
Update Contact
List Contact
Get single contact
Delete Contact
Request tanpa token
Okei sampai disini sudah selesai, semoga bermanfaat 🙂
Referensi
https://laravel-jwt-auth.readthedocs.io/en/latest/quick-start/