Click Below to subscribe

How to Implement JWT Authentication in Laravel 2022

JSON Web Token (jwt) is an open standard that allows two parties to securely send data as JSON objects.

In this article, we will implement jwt authentication from scratch(laravel 8).

1. Let's create a new laravel project.

 composer create-project laravel/laravel laravel-jwt

2. After creating the project install jwt package.

composer require tymon/jwt-auth

3. After completing the installation. publish the jwt config file.

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

This will create a jwt.php file inside the config folder. this file contains all configurations of jwt.

4. Generate a secret key.

php artisan jwt:secret

This will update your .env file with something like JWT_SECRET=something

5. Update your database credentials in .env file.

DB_DATABASE=laravel-jwt // your database name
DB_USERNAME=root        // your database username 
DB_PASSWORD=            // your database password

6. We need to create a users table. for this, we gonna use laravel migration. also, you can manually create a users table that contains email and password columns. 

Laravel by default having a users migration file (2014_10_12_000000_create_users_table.php) inside database/migrations directory

hit this command to create users table.

php artisan migrate

7. After migration you need to update User model. you need to implement the Tymon\JWTAuth\Contracts\JWTSubject contract on your User model, which requires that you implement the 2 methods getJWTIdentifier() and getJWTCustomClaims().

<?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 Laravel\Sanctum\HasApiTokens;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use HasApiTokens, 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 [];
    }
}


8. Update your Auth Guard.

Note:- If you are using laravel for both web and api's then don't set jwt as the default guard.

Because for the web we need session driver for handling web session's

So based on the above scenario you can setup the default guard to jwt.

go to the config folder and open auth.php. after that update guards array like this.

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

After adding jwt guard you can make this guard to default guard.

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

What is the benefit of setting this guard as default?

auth()->attempt($credentials)      // after setting as default

auth('api')->attempt($credentials) // without setting as default

9. Create the AuthController.

php artisan make:controller Api/AuthController

Note:- Here Api is the folder name.

This will create AuthController.php inside app/Http/Controllers/Api directory

10. Update AuthController.php

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;

class AuthController extends Controller
{

    public function register(Request $request)
    {
        $user = new User();
        $user->name = $request->name;
        $user->email = $request->email;
        $user->password = bcrypt($request->password);
        $user->save();
        return response()->json(['data' => $user]);
    }

    public function login()
    {
        $credentials = request(['email', 'password']);

        if (!$token = auth('api')->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
        return $this->getToken($token);
    }

    public function getUser()
    {
        return response()->json(auth('api')->user());
    }

    public function logout()
    {
        auth('api')->logout();
        return response()->json(['message' => 'Logged out successfully']);
    }

    public function refreshToken()
    {
        return $this->getToken(auth('api')->refresh());
    }

    protected function getToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }
}

11. Go to app/Providers and open RouteServiceProvider.php. uncomment $namespace;

protected $namespace = 'App\\Http\\Controllers';

12. Create the ApiMiddleware.

php artisan make:middleware ApiMiddleWare

This will create ApiMiddleware.php file inside app/Http/Middleware

13. Update ApiMiddleware.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;

class ApiMiddleWare
{
    public function handle(Request $request, Closure $next)
    {
        JWTAuth::parseToken()->authenticate();
        return $next($request);
    }
}

14. Go to app/Http and open Kernel.php. Add ApiMiddleware in $routeMiddleware array.

protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'auth.jwt'=> \App\Http\Middleware\ApiMiddleWare::class
    ];

15. Go to routes folder and update api.php

<?php

use Illuminate\Support\Facades\Route;

// namespace Api is Folder name
// prefix all api urls with auth - example:- auth/register
Route::group(['prefix' => 'auth', 'namespace' => 'Api'], function () {

    Route::post('register', 'AuthController@register');
    Route::post('login',    'AuthController@login');
    
    // protected routes
    Route::group(['middleware' => 'auth.jwt'], function () {
        Route::get('logout',    'AuthController@logout');
        Route::get('refresh',   'AuthController@refreshToken');
        Route::get('user',      'AuthController@getUser');
    });
    
    // fallback optional
    Route::any('{segment}', function () {
        return response()->json([
            'error' => 'Invalid url.'
        ]);
    })->where('segment', '.*');
});

16. Go to app/Exceptions and Handler.php. update register function.

<?php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that are not reported.
     *
     * @var array<int, class-string<Throwable>>
     */
    protected $dontReport = [
        //
    ];

    /**
     * A list of the inputs that are never flashed for validation exceptions.
     *
     * @var array<int, string>
     */
    protected $dontFlash = [
        'current_password',
        'password',
        'password_confirmation',
    ];

    /**
     * Register the exception handling callbacks for the application.
     *
     * @return void
     */
    public function register()
    {
        $this->renderable(function (Throwable $e, $request) {
            if ($request->is('api/*')) {
                $error = '';
                if ($e instanceof TokenInvalidException) {
                    $error = 'Token is invalid';
                } else if ($e instanceof TokenExpiredException) {
                    $error = 'Token is expired';
                } else {
                    $error = 'Unauthorized';
                }
                return response()->json(['error' => $error], 401);
            }
        });
    }
}

Finally Open Postman or any REST API Client and test these API's

Checkout my full laravel-jwt example.

https://github.com/ultimateakash/laravel-jwt

If you need any help, don't hesitate to comment below.

Thanks.

Leave Your Comment