Building Laravel / Lumen REST APIs with JWT Authentication

Building laravel 6 REST APIs with JWT Authentication

let’s start building a robust restful API in Laravel using JWT Authentication. JSON Web Token (JWT) is an open standard that allows two parties to securely send data and information as JSON objects. It makes it convenient to authorise and verify clients accessing API resources.

Getting Started A New Project

Install a new Laravel project using Composer’s create-project command

composer create-project --prefer-dist laravel/laravel AUTH-JWT

Add the database details to the .env file.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=firstAuthApp
DB_USERNAME=root
DB_PASSWORD=

Let’s install the package via Composer

composer require tymon/jwt-auth:dev-develop --prefer-source

publish the package’s config using the following command

php artisan vendor:publish

After that select index of Provider: Tymon\JWTAuth\Providers\LaravelServiceProvider and hit in terminal

Now, lets generate a secret key that will encrypt our token

php artisan jwt:secret

This will generate an encrypted secret key & update the .env file with JWT_SECRET = SOME_TOKEN

Step 4 : Registering Middleware

Register auth.jwt middleware in app/Http/Kernel.php

protected $routeMiddleware = [
.
.
.
'auth.jwt' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class
];

Step 5 : Create Api Routes

paste this following code to this path routes/api.php

$router->post('register', 'Api\Auth\AuthController@register');
$router->post('login', 'Api\Auth\AuthController@login');

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

Route::post('refresh', 'Api\Auth\AuthController@refresh');
Route::post('user', 'Api\Auth\AuthController@getAuthUser');
Route::post('logout', 'Api\Auth\AuthController@logout');
Route::post('addTask', 'TaskController@store');
Route::get('allTasks', 'TaskController@index');
Route::get('task/{id}', 'TaskController@show');
Route::put('task/{id}', 'TaskController@update');
Route::delete('deleteTask/{id}', 'TaskController@destroy');

});

Step 6: Update User Model

Now open user model in app/User.php and paste this following code to make changes.

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
use Notifiable;

protected $fillable = [
'name', 'email', 'password',
];

protected $hidden = [
'password', 'remember_token',
];

protected $casts = [
'email_verified_at' => 'datetime',
];

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

public function getJWTCustomClaims()
{
return [];
}
}

Step 7: Configure Auth Guard in config/auth.php

Modify api driver as jwt

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

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

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

Step 8: Configure a better API Response format [ Good to know 😇 ]

Create a folder HelperService under app folder & then create Helpers.php in app/HelperService. Add below code for better API Response format

namespace App\HelperService;

class Helpers
{
public function response(bool $isSuccess, string $report, $details, $errors, $responseCode)
{
return response()->json([
'success' => $isSuccess,
'responseCode' => $responseCode,
'errors' => $errors,
'report' => $report,
'details' => $details
], $responseCode, [], JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
}
}

Step 9: Add Authenticate controller [ Authentication Logic ]

php artisan make:controller Api/Auth/AuthController

Now open this AuthController.php and paste this below code in app/Api/Auth/

namespace App\Http\Controllers\Api\Auth;

use App\HelperService\Helpers;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\Response;
use Tymon\JWTAuth\Contracts\Providers\Auth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Facades\JWTAuth;


class AuthController extends Controller
{
private $loginAfterSignUp = true;
private $helpers;
private $created;
private $createdCode;
private $badRequest;
private $badRequestCode;
private $unauthorized;
private $unauthorizedCode;
private $accepted;
private $acceptedCode;
private $internalError;
private $internalErrorCode;

public function __construct(
Helpers $helpers
)
{
$this->created = 'Created';
$this->createdCode = 201;
$this->badRequest = 'Bad Request';
$this->badRequestCode = 400;
$this->unauthorized = 'Unauthorized';
$this->unauthorizedCode = 401;
$this->accepted = 'Accept';
$this->acceptedCode = 200;
$this->internalError = 'Internal Error';
$this->internalErrorCode = 500;
$this->helpers = $helpers;
}

protected function createNewToken($token)
{
return [
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => JWTAuth::factory()->getTTL() * 60
];
}

public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string',
'email' => 'required|email|unique:users',
'password' => 'required|confirmed'
]);

if ($validator->fails()) {
return response()->json($validator->errors());
}

$user = new User();
$user->name = $request->get('name');
$user->email = $request->get('email');
$plainPassword = $request->get('password');
$user->password = Hash::make($plainPassword);
$user->save();

$response = [
"user" => $user
];
return $this->helpers->response(true, $this->created, $response, null, $this->createdCode);
}

public function login(Request $request)
{
$rules = [
'email.required' => 'Email can not be empty.',
'password.required' => 'Password can not be empty.'
];
$validator = Validator::make($request->all(), [
'email' => 'required|string',
'password' => 'required|string',
], $rules);

if ($validator->fails()) {
$errors = $validator->errors();
return $this->helpers->response(false, $this->badRequest, null, $errors, $this->badRequestCode);
}

$credentials = $request->only("email", "password");
$token = null;

if (!$token = JWTAuth::attempt($credentials)) {
return $this->helpers->response(false, $this->unauthorized, null, null, $this->unauthorizedCode);
}


return $this->helpers->response(true, $this->accepted, $this->createNewToken($token), null, $this->acceptedCode);
}

public function logout(Request $request)
{
$this->validate($request, [
'token' => 'required'
]);

try {
JWTAuth::invalidate($request->token);
$details = "Logged out";
return $this->helpers->response(true, $this->accepted, $details, null, $this->acceptedCode);
} catch (JWTException $exception) {
$details = "Please try again!";
return $this->helpers->response(false, $this->internalError, $details, null, $this->internalErrorCode);
}
}

public function getAuthUser(Request $request)
{
$this->validate($request, [
'token' => 'required'
]);

$user = JWTAuth::authenticate($request->token);
$response = [
"user" => $user
];

return $this->helpers->response(true, $this->accepted, $response, null, $this->acceptedCode);
}

public function refresh()
{
$refreshToken = $this->createNewToken(JWTAuth::refresh());
return $this->helpers->response(true, $this->accepted, $refreshToken, null, $this->acceptedCode);
}

}

Step 10: Please do following artisan command

php artisan migratephp artisan serve

Step 11: Register New User, Generate Token, Refresh Token, Get User Details & Log out 😃

Here I’m using Insomnia tool. we can use Insomnia or Postman or other tools too to test the REST APIs

Registering New User

This is a post request with name, email, password & password_confirmation as parameters. And in header put Content-Type: application/x-www-form-urlencoded

Login & Generate Token

This is a post request with email & password parameters. And in header put Content-Type: application/x-www-form-urlencoded

Refresh Token

This is a post request with email & password parameters. And in header put Content-Type: application/x-www-form-urlencoded, Authorization: bearer [previouslyGeneratedToken]

Get User Details

This is a post request with token field. And in header put Content-Type: application/x-www-form-urlencoded

Log out

This is a post request with token field. And in header put Content-Type: application/x-www-form-urlencoded

✔️ Good to Know: We’ll be able to use token for one time only while logging out or refreshing token API. Once one token is used, we cannot re-use ❌ that token for refreshing or logging out purposes again & again 😊

Step 12: Create Task Model, Database & Controller

It will create a new database migration file create_tasks_table.php in database/migrations directory.

public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('details');
$table->timestamps();
});
}

Now please do php artisan migrate in terminal.

Update below code in app/Task.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
protected $fillable = [
'title', 'details'
];
}

Now go to TaskController.php file and paste this following code in app/Http/Controllers/TaskController.php

namespace App\Http\Controllers;

use App\HelperService\Helpers;
use App\Task;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class TaskController extends Controller
{
private $loginAfterSignUp = true;
private $helpers;
private $created;
private $createdCode;
private $badRequest;
private $badRequestCode;
private $unauthorized;
private $unauthorizedCode;
private $accepted;
private $acceptedCode;
private $internalError;
private $internalErrorCode;


public function __construct(
Helpers $helpers
)
{
$this->created = 'Created';
$this->createdCode = 201;
$this->badRequest = 'Bad Request';
$this->badRequestCode = 400;
$this->unauthorized = 'Unauthorized';
$this->unauthorizedCode = 401;
$this->accepted = 'Accept';
$this->acceptedCode = 200;
$this->internalError = 'Internal Error';
$this->internalErrorCode = 500;
$this->helpers = $helpers;
}



public function index()
{
$tasks = Task::get();
$response = [
'tasks' => $tasks
];
return $this->helpers->response(true, $this->accepted, $response, null, $this->acceptedCode);
}

public function create()
{
//
}

public function store(Request $request)
{
$rules = [
'title.required' => 'Title can not be empty.',
'details.required' => 'Details can not be empty.'
];

$validator = Validator::make($request->all(), [
'title' => 'bail|required',
'details' => 'required',
], $rules);

if ($validator->fails()) {
$errors = $validator->errors();
return $this->helpers->response(false, $this->badRequest, null, $errors, $this->badRequestCode);
}

$task = new Task();
$task->title = $request->title;
$task->details = $request->details;


if ($success = $task->save()) {
return $this->helpers->response(true, $this->created, $task, null, $this->createdCode);
} else {
$details = "Task insertion failed!";
return $this->helpers->response(false, $this->internalError, $details, null, $this->internalErrorCode);
}

}

public function show($id)
{
if (Task::where('id', $id)->exists()) {
$task = Task::firstWhere('id', $id);
$response = [
'task' => $task
];
} else {
$response = 'Task not found';
}

return $this->helpers->response(true, $this->accepted, $response, null, $this->acceptedCode);
}

public function edit(Task $task)
{
//
}

public function update(Request $request, $id)
{
$response = null;

$rules = [
'title.required' => 'Title name can not be empty.',
'details.required' => 'Details name can not be empty.'
];
$validator = Validator::make($request->all(), [
'title' => 'required',
'details' => 'required'
], $rules);

if ($validator->fails()) {
return $this->helpers->response(false, $this->badRequest, null, null, $this->badRequestCode);
}

$title = $request->title;
$details = $request->details;

$taskId = Task::where('id', $id);

if ($taskId->exists()) {
$task = $taskId->update(['title' => $title, 'details' => $details]);
if ($task) {

$response = [
'id' => $id,
'title' => $title,
'details' => $details
];
}
} else {
$response = 'Task not found';
}

return $this->helpers->response(true, $this->accepted, $response, null, $this->acceptedCode);
}

public function destroy($id)
{
$taskId = Task::where('id', $id);
if ($taskId->exists()) {
$taskId->delete();
$response = [
'deleted' => $id
];
} else {
$response = "Task not found";
}

return $this->helpers->response(true, $this->accepted, $response, null, $this->acceptedCode);
}
}

Step 12: Add Task, Get All Task, Get Specific Task, Update Task & Delete Task 😃

Add Task

This is a post request with title & details as parameters. And in header put Content-Type: application/x-www-form-urlencoded, Authorization: bearer [previouslyGeneratedToken]

Get All Tasks

This is a get request & in header put Authorization: bearer [previouslyGeneratedToken]

Get Specific Task

This is a get request too & in header put Authorization: bearer [previouslyGeneratedToken]

Update Task

This is a put request with title & details as parameters. And in header put Content-Type: application/x-www-form-urlencoded, Authorization: bearer [previouslyGeneratedToken]

Delete Task

This is a delete request & in header put Authorization: bearer [previouslyGeneratedToken]

That’s it. This tutorial will help you to understand how to make REST API authentication using jwt. Also, the complete source code for the project built in this tutorial is on my GitHub. After git clone, do composer install & then composer dump-autoload in terminal. Have fun 🔥 💪

If you have any query, please put in below.

Software Dev