DEV Community

Funke Olasupo
Funke Olasupo

Posted on

JWT-AUTH Exception Handling In Laravel 8

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

PS: A user model and migration already exists because they come default with laravel in App/Models and database/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=
Enter fullscreen mode Exit fullscreen mode
  • 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();
        });
    }
Enter fullscreen mode Exit fullscreen mode
  • Finally we can run our migrations to our database.

We run migrations to our database with this Artisan CLI command:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Step 3: Set up Register method User Controller.

  • We create controllers with this Artisan CLI command:
php artisan make:controller UserController
Enter fullscreen mode Exit fullscreen mode
  • Implement the User model in the UserController.
use App\Models\User;
Enter fullscreen mode Exit fullscreen mode
  • 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);
    }
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • 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,
    ],
Enter fullscreen mode Exit fullscreen mode
  • Run the following command to publish the package config file:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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',
    ],
],
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  • 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);
    }
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  • 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);
    }
Enter fullscreen mode Exit fullscreen mode
  • Add the middleware to the array of routeMiddleware in the kernel.php like this:
protected $routeMiddleware = [
        .......
        'auth.jwt'=>\App\Http\Middleware\JwtMiddleWare::class,
    ];
Enter fullscreen mode Exit fullscreen mode
  • 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;
Enter fullscreen mode Exit fullscreen mode

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);
        });

    }
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  • 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');

Enter fullscreen mode Exit fullscreen mode

Congratulations, you have successfully built an API with JWT-AUTH😍, Now let us test it with postman.

Step 10: Test with Postman.

  • This shows that our registration works with valid input.
    Alt Text

  • This shows that our login works with valid input.
    Alt Text

  • 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:

Alt Text

Let's verify if our Exception Handling works.

For JWTException, we do not pass token with the request:

Alt Text

For TokenInvalidException, we put in the wrong token:
Alt Text

  • For TokenExpiredException, we resend that token after a long period of time depending on the time to live for the token.

Alt Text

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)

Collapse
 
teckel12 profile image
Tim Eckel

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