Building Laravel / Lumen 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
Step 1: Install Laravel
Install a new Laravel project using Composer’s create-project
command
composer create-project --prefer-dist laravel/laravel AUTH-JWT
Step 2: Setting up Database
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=
Step 3: Install tymondesigns/jwt-auth Package
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, let’s 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.