How to Implement JWT Auth in Laravel 9
Introduction
This is a friendly introduction to Laravel 9 API authentication using JWT. In this tutorial we will learn how to use JSON Web Token (JWT) to secure REST APIs in Laravel 9. We will be using a third-party library called “php-open-source-saver/jwt-auth” to achieve this. Creating authentication in Laravel is very easy as most of the common functionalities needed in a web application are already implemented out of the box.
Goal
The reader should be able to create secure REST APIs in Laravel using JWT after going through this tutorial
Requirement
Basic knowledge of the Laravel framework is required to take this tutorial as this is not an introduction to Laravel tutorial
What is JSON Web Token
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
When should you use JSON Web Tokens
Here are some scenarios where JSON Web Tokens are useful:
• Authorization: This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. Single Sign-On is a feature that widely uses JWT nowadays, because of its small overhead and its ability to be easily used across different domains.
• Information Exchange: JSON Web Tokens are a good way of securely transmitting information between parties. Because JWTs can be signed—for example, using public/private key pairs—you can be sure the senders are who they say they are. Additionally, as the signature is calculated using the header and the payload, you can also verify that the content hasn't been tampered with.
What is the JSON Web Token structure?
In its compact form, JSON Web Tokens consist of three parts separated by dots (.), which are:
• Header
• Payload
• Signature
Therefore, a JWT typically looks like the following.
xxxxx.yyyyy.zzzzz
How Does It Work
The simple explanation as to how JWT authentication work is when a user attempt to log in with their correct credentials (i.e email and password), a token is generated and sent back to the client-side, the client-side then stores the token and use it to access protected routes.
Let's Get To It Shall We
Table of content
- Fresh Laravel Install
- Create Your Database
- Connect To The Database
- Run Migration To Create User Table
- Install JWT Auth Package
- Configure Auth Guard
- Modify User Model
- Create Controller
- Add API Routes
- Test Endpoints With Postman
- Conclusion
Fresh Laravel Install
We will kick things off by installing a new Laravel 9 project
Run the below commands to install and navigate to your fresh Laravel project
composer create-project laravel/laravel laravel-jwt
cd laravel-jwt
Create Your Database
Create a MySQL database with the name laravel-jwt, I am using XAMMP but whatever you are using will work just fine.
Connect To The Database
For your Laravel application to be able to interact with the new database you just created you need to establish a connection, add your database credentials to the .env file to connect
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-jwt
DB_USERNAME=root
DB_PASSWORD=
Run Migration To Create User Table
Laravel comes with the User table migration all we need to do is to run the migration to create the table in our database, we do not need to create any other table, the user table is sufficient for the scope of this tutorial. Run the command below to create the users table
php artisan migrate
Install JWT Auth Package
Now that our database is all set up its time to install and configure the Laravel jwt auth package, will be using “php-open-source-saver/jwt-auth” a fork of “tymondesign/jwt-auth”, the reason for the choice is simple as “tymondesign/jwt-auth” seems to have been abandoned and is not compatible with Laravel 9
Run the below command to install the latest version of the package
composer require php-open-source-saver/jwt-auth
Next, we have to publish the package configurations run the below command to copy the jwt configuration file from vendor to confi/jwt.php
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
a secret key needs to be generated to handle the token encryption run this command to achieve that
php artisan jwt:secret
This will update your .env file with something like
JWT_SECRET=foobar
It is the key that will be used to sign your tokens
Configure Auth Guard
Inside the config/auth.php file you will need to make a few changes to configure Laravel to use the jwt guard to power your application authentication.
Make the following changes to the file:
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
Here we are telling the api guard to use the jwt driver, and we are setting the api guard as the default.
We can now use Laravel's built-in Auth system, with jwt-auth doing the work behind the scenes!
Modify User Model
Firstly you need to implement the PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject contract on your User model, which requires that you implement the 2 methods getJWTIdentifier() and getJWTCustomClaims().
Replace the existing code in app/Models/User.php with the following code
<?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 PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'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 [];
}
}
That is it for our model setup
Create Controller
Next, we create a controller to handle the core logic of the authentication process
Run this command to generate the controller
php artisan make:controller AuthController
replace the content of the controller with the following code
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
class AuthController extends Controller
{
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login','register']]);
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
]);
$credentials = $request->only('email', 'password');
$token = Auth::attempt($credentials);
if (!$token) {
return response()->json([
'status' => 'error',
'message' => 'Unauthorized',
], 401);
}
$user = Auth::user();
return response()->json([
'status' => 'success',
'user' => $user,
'authorisation' => [
'token' => $token,
'type' => 'bearer',
]
]);
}
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::login($user);
return response()->json([
'status' => 'success',
'message' => 'User created successfully',
'user' => $user,
'authorisation' => [
'token' => $token,
'type' => 'bearer',
]
]);
}
public function logout()
{
Auth::logout();
return response()->json([
'status' => 'success',
'message' => 'Successfully logged out',
]);
}
public function me()
{
return response()->json([
'status' => 'success',
'user' => Auth::user(),
]);
}
public function refresh()
{
return response()->json([
'status' => 'success',
'user' => Auth::user(),
'authorisation' => [
'token' => Auth::refresh(),
'type' => 'bearer',
]
]);
}
}
A brief explanation of what is going on in the AuthController
Constructor: we defined a constructor method in our controller class so that we can use the auth:api middleware within it, the middleware prevent unauthenticated access to certain methods within the controller.
Login: the login method authenticate the user using their email and password, the Auth facades attempt() method returns the jwt token once the user is successfully authenticated, the generated token is retrieved and returned as JSON with the user object
Register: the register method creates the user record and logs the user in with token generations as well
Logout: the logout method invalidate the user auth token
Refresh: the refresh method invalidates the current logged in user and generate a new token
Me: the me method returns the user profile or in this case the user object
Add API Routes
To access our newly created methods we need to define our API routes, to do this navigate to routes/api.php and replace the content with the following
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
Route::controller(AuthController::class)->group(function () {
Route::post('login', 'login');
Route::post('register', 'register');
Route::post('logout', 'logout');
Route::post('refresh', 'refresh');
Route::get('me', 'me');
});
Note we are using Laravel 9 syntax here, you will need to declare your route the normal way if you are using lower versions of Laravel
Test End Points With Postman
Before we move to Postman and start testing our API endpoints we need to start our Laravel application first
Run the below command to start your Laravel application
php artisan serve
now we are ready to begin testing;
start your postman app
here are the lists of endpoints we will be testing
- Register: localhost:8000/api/register. Method: POST
- Login: localhost:8000/api/login. Method: POST
- Logout: localhost:8000/api/logout. Method: POST
- Refresh: localhost:8000/api/refresh. Method:POST
- Me: localhost:8000/api/me. Method: GET
Register API
Start your Postman application add the registration API in the address bar, select the POST HTTP request method, select form-data in the body tab and add the name, email and password input fields, click on the Send button to see the server response
Login API
Add the email and password to the input field click Send to see the response
Me, Refresh and Logout
The Me, Refresh and Logout endpoints are all protected by the auth:api middleware and hence requires that you send a valid token with the authorization header, so copy the token from your login response go to the authorization tab, select bearer token and paste it the input provided.
Conclusion!
Yes, you made it to the end, in this article we learned how to create REST API authentication with JWT, the full code for this project is available on GitHub