Securing Laravel 10 API using JWT.

Jurin Liyun
6 min readAug 23, 2023

It was a privilege for me to conduct Laravel training at ILP Selandar. I learned from them so much during the training especially on the spirit of learning Laravel framework. By this regard, so i write this simple tutorial hoping that this will help more developers.

I hope my training participants in ILP Selandar will see this tutorial on how to use JWT for securing their Laravel API. And for those who are new in Laravel or API, this might be useful for you.

Lets get started.

What is JWT?

JSON Web Tokens (JWT), pronounced “jot”, are a standard since the information they carry is transmitted via JSON. We can read more about the draft, but that explanation isn’t the most pretty to look at.

JSON Web Tokens work across different programming languages: JWTs work in .NET, Python, Node.js, Java, PHP, Ruby, Go, JavaScript, and Haskell. So you can see that these can be used in many different scenarios.

JWTs are self-contained: They will carry all the information necessary within itself. This means that a JWT will be able to transmit basic information about itself, a payload (usually user information), and a signature.

JWTs can be passed around easily: Since JWTs are self-contained, they are perfectly used inside an HTTP header when authenticating an API. You can also pass it through the URL. (The Anatomy of a JSON Web Token | DigitalOcean)

First you must have Laravel project ready. Second, grab this package from packagist and install it. Copy and paste the following command.

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

After package installation make sure to run the following code in order to get the config jwt.php

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

Generate the jwt secret key by executing the following command.

php artisan jwt:secret

The secret key will be added inside the .env file as follows

JWT_SECRET=UZOcmpDPHccDerQhh22RpEFip2fNi1IgeXkN6gun0HcMvjI75MlrwI98lyeFNPDV

JWT_ALGO=HS256

Now that you have all the code ready, we now create the controller for api authentication.

php artisan make:controller AuthController

Setting Up the Auth.php, User.php

Open auth.phpconfig, add the following code in guards section.

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

After adding the config, go to yourUser.php model and add the following code to implement two methods as follows:

use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject; // this sould be imported

//add the JWTSubject implementation class
class User extends Authenticatable implements JWTSubject
{

//put these methods at the bottom of your class body

public function getJWTIdentifier()
{
return $this->getKey();
}

public function getJWTCustomClaims()
{
return [
'email'=>$this->email,
'name'=>$this->name
];
}
}

Next, open up the AuthController.php. In your controller please add login,register,refresh,logout methods.

The original convention of Auth implementation was Auth::user(). The implementation logic will automatically fallback to default guards defined in auth.php config. So if the default is not changed, you can Auth::guard('api')->user() to directly specifying which guard to use. In our example we will use the api guard defined in auth.php.

<?php

namespace App\Http\Controllers;

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

class AuthController extends Controller
{

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

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::guard('api')->login($user);
return response()->json([
'status' => 'success',
'message' => 'User created successfully',
'user' => $user,
'authorisation' => [
'token' => $token,
'type' => 'bearer',
]
]);
}

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

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

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

}

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


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



}

Now lets define the routes for api auth. Open up the code located in app->routes->api.php and add the following codes

use App\Http\Controllers\AuthController;

Route::post('register',[AuthController::class,'register']);
Route::post('login', [AuthController::class,'login']);
Route::post('refresh', [AuthController::class,'refresh']);
Route::post('logout', [AuthController::class,'logout']);

If everything is ok, then you can run your server by firing the command below.

php artisan serve

Testing the api

To test the new security feature, we must install postman or insomnia. If you haven’t install them yet, please click the following link to download and then install.

Test login endpoint

http://127.0.0.1:8000/api/login. The email and password must match with your database user. The following convention was following the Auth implementation for laraval.

Jwt token generated after successfull login

The token that was generated by the endpoint can be use to authenticate any api endpoint that are tied with the auth::api middleware.

To make it more clear, we can now add,

php artisan make:model Room -m //this will generate the migration while creating the modelb

add the following code to your migration

public function up(): void
{
Schema::table('rooms', function (Blueprint $table) {
$table->id();
$table->string('room_name')->nullable();
$table->integer('room_capacity')->nullable();
$table->string('room_status')->nullable();
});
}

Now create the controller for the roommodel with --resource option to generate standard laravel convention for resource (index,show,edit,etc).

php artisan make:controller Api/RoomController --resource
<?php

namespace App\Http\Controllers\Api;

use App\Models\Room;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Http\Resources\v1\RoomResource;


class RoomController extends Controller
{
public function __construct()
{
$this->middleware('auth:api');
}
/**
* Display a listing of the resource.
*/
public function index()
{
return RoomResource::collection(Room::all());
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}

/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{

$room = Room::create($request->all());



return response()->json([
'status' => true,
'message' => "Room Created successfully!",
'room' => $room
], 200);

}

/**
* Display the specified resource.
*/
public function show(Room $room)
{
return response()->json(
Room::find($room)
);
}

/**
* Show the form for editing the specified resource.
*/
public function edit(Room $room)
{
//
}

/**
* Update the specified resource in storage.
*/
public function update(Request $request, Room $room)
{
//
}

/**
* Remove the specified resource from storage.
*/
public function destroy(Room $room)
{
//
}

}

Now lets defined our route for the room api. Open up the api.php code located in your app->route, and add the following code

Route::apiResource('rooms', RoomController::class);
php artisan optimize:clear

Before we can test our api, please make sure you add some data in your table.

php artisan tinker

> App\Models\Rooms::insert([
"id"=> 1,
"room_name"=>"BILIK 1",
"room_capacity"=> 2,
"room_status"=> "Kosong"
]);

Now its time to test our api. Copy the previous generated token and paste it to Bearer token in your insomnia client.

{
"data": [
{
"id": 1,
"room_name": "BILIK 1",
"room_capacity": 2,
"room_status": "Kosong"
}
]
}

Now if you try to find out whats inside your token you can also test it in https://jwt.ms

Now all settled.

If you think that this article helps you, claps would be your generous appreciation. You might share it with your friend who need some help around this topic.

Your comment also accepted for me to improve my technical writing so that it will help others.

Thank you.

--

--

Jurin Liyun

Developer, Trainer , Consultant, Father of two children