Are you experiencing difficulties with handling JWT-AUTH Exceptions in Laravel 8 or you're curious to know how it works? Throughout this article, I will be guiding you through an easy process of understanding it.
INTRODUCTION
JWT-AUTH -> (JSON Web Token Authentication For Laravel and Lumen).
JWT is mainly used for authentication. After a user logs in to an application, the application will create a JWT and send it back to the user. Subsequent requests by the user will include the JWT. The token tells the server what routes, services, and resources the user is allowed to access
We will be creating a basic Register and Login API where authorized users can fetch their information from the database with JWT implemented and then handle some exceptions. Let's begin🤩, I hope you enjoy this guide.
Step 1: Creating a new laravel project
You can create a new laravel project with the following command:
laravel new jwt_exception_handling
Step 2: Set up Model and Migrations for Users
- We can set up models and migrations simultaneously like this:
php artisan make:model User -m
PS: A user model and migration already exists because they come default with laravel in
App/Models
anddatabase/migrations
directory respectively.
- Set Up Database Connection
This is done in the .env
file based on where you are serving your database like this:
PS: These configurations are for my own local machine, yours may be different.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=jwt_handling
DB_USERNAME=root
DB_PASSWORD=
- Next is to set up the schema for our migration but since our
create_users_table.php
migration file comes default with laravel, we have nothing to do here.
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
- Finally we can run our migrations to our database.
We run migrations to our database with this Artisan CLI command:
php artisan migrate
Step 3: Set up Register method User Controller.
- We create controllers with this Artisan CLI command:
php artisan make:controller UserController
- Implement the User model in the UserController.
use App\Models\User;
- Define the
register()
method in the UserController class.
The register()
method validates a users' input and creates a user if the user credentials are validated.
public function register(Request $request)
{
$this->validate($request, [
'name'=>'required',
'email'=>'required|email|unique:users',
'password'=>'required'
]);
$user = new User([
'name'=> $request->input('name'),
'email'=> $request->input('email'),
'password'=> bcrypt($request->input('password'))
]);
$user->save();
return response()->json([
'message'=>'Successfully Created user'
],201);
}
- Next, we want to define the
login()
method but before that, we need to import JWT.
Step 4: Import JWT-AUTH.
- To pull in the latest version of jwt-auth, run this command:
composer require tymon/jwt-auth
- Add the service provider to the providers array and alias to the aliases array in the
config/app.php
config file like this:
'providers' => [
...
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
],
'aliases' => [
'JWTAuth'=>Tymon\JWTAuth\Facades\JWTAuth::class,
],
- Run the following command to publish the package config file:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
You should now have a config/jwt.php
file that allows you to configure the basics of this package.
- There is a helper command to generate a key that will be used to sign your tokens:
php artisan jwt:secret
This will update your .env file
with something like JWT_SECRET = (key generated).
Step 5: Set up JWT-AUTH in our project.
- Update your User model
Firstly,we need to implement the Tymon\JWTAuth\Contracts\JWTSubject
contract on your User model, which requires you implement 2 methods getJWTIdentifier()
and getJWTCustomClaims()
.
Your User model should be updated like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
}
- Next, we need to configure Auth Gaurd
You'll need to update the config/auth.php
file with these changes to configure Laravel to use the jwt
guard to power your application authentication.
PS: Update only these arrays and leave the others.
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
...
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
Step 6: Set Up Login Method in UserController
- First we will implement JWT and its exception to our UserController.
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
- Now back to our UserController class, we define the
login()
method for users to login.
This will validate users' input. The auth method accepts a request containing an email and password which will be checked against the user database for authentication.
Once you authenticate, the controller returns a JWT that you need to keep.
public function login(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
'password' => 'required'
]);
$credentials = $request->only('email', 'password');
try {
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json([
'error' => 'Invalid Credentials'
], 401);
}
} catch (JWTException $e) {
return response()->json([
'error' => 'Could not create token'
], 500);
}
return response()->json([
'token' => $token
], 200);
}
Step 7: Set up getUser Method in the UserController.
Still, in our UserController, we define getUser()
method which is responsible for getting users' personal information from the database.
public function getUser(){
$user = auth('api')->user();
return response()->json(['user'=>$user], 201);
}
Step 8: Exception Handling
- Firstly, we create a route middleware to parse the token of an authenticated user. We make a middleware with this Artisan CLI command:
php artisan make:middleware JWTMiddleWare
A class of JWTMiddleware atApp/Http/Middleware/JWTMiddleware.php
directory will be created.
- Next, we implement JWTAuth in the JWTMiddleware.php like this:
use JWTAuth;
- The middleware comes with a default method handle and now we parse the generated token of an authenticated user like this:
public function handle(Request $request, Closure $next)
{
$user = JWTAuth::parseToken()->authenticate();
return $next($request);
}
- Add the middleware to the array of routeMiddleware in the
kernel.php
like this:
protected $routeMiddleware = [
.......
'auth.jwt'=>\App\Http\Middleware\JwtMiddleWare::class,
];
- Exception Handling in Handlers Class.
We will create the handlers for our exception in Handler.php
in app/Exceptions
directory.
Our exceptions will be in the register()
method that comes default with Handler.php
.
We will be handling the following exceptions: Invalid token, Expired Token, JWTException but we need to implement these Exceptions in Handler.php
.
use Response;
use Exception;
use Throwable;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
Now, to the register()
method:
public function register()
{
$this->renderable(function(TokenInvalidException $e, $request){
return Response::json(['error'=>'Invalid token'],401);
});
$this->renderable(function (TokenExpiredException $e, $request) {
return Response::json(['error'=>'Token has Expired'],401);
});
$this->renderable(function (JWTException $e, $request) {
return Response::json(['error'=>'Token not parsed'],401);
});
}
Step 9: Set Up Routes.
Seeing we're are creating an API, we will set up our routes in routes/api.php
.
- We need to implement our UserController first.
use App\Http\Controllers\UserController;
- Then we set up our routes.
We will be adding the JWTMiddleware to the routes for getting users' details, this way we can see the work of Exception Handling.
Route::post('/register', [
UserController::class, 'register'
]);
Route::post('/login', [
UserController::class, 'login'
]);
Route::get('/user', [
UserController::class, 'getUser'
])->middleware('auth.jwt');
Congratulations, you have successfully built an API with JWT-AUTH😍, Now let us test it with postman.
Step 10: Test with Postman.
Now to get users' information, we need to pass the token alongside the request and we do this by setting Authorization to Bearer (Token) in our headers like this:
Let's verify if our Exception Handling works.
For JWTException, we do not pass token with the request:
For TokenInvalidException, we put in the wrong token:
- For TokenExpiredException, we resend that token after a long period of time depending on the time to live for the token.
Congratulations on building an API and implementing JWT-AUTH and also handling some exceptions😍.
Guess what?😊 The code for this practice is open-source here on my Github.
These are my humble opinions so please, if you have any contradicting or buttressing opinions, do well to reach me on Twitter.
If you need more information on jwt-auth, visit the official documentation.
Thank you for reading till the end🤝🤩😍
Top comments (1)
Doesn't work. When signing in it throws the following error:
Tymon\JWTAuth\JWTGuard::login(): Argument #1 ($user) must be of type Tymon\JWTAuth\Contracts\JWTSubject, Illuminate\Auth\GenericUser given, called in /var/www/html/vendor/tymon/jwt-auth/src/JWTGuard.php on line 124