DEV Community

Cover image for How to Implement JWT Auth in Laravel 9
Solomon Onuche
Solomon Onuche

Posted on • Updated on

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

  • XAMPP

  • composer

  • Postman

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

  1. Fresh Laravel Install
  2. Create Your Database
  3. Connect To The Database
  4. Run Migration To Create User Table
  5. Install JWT Auth Package
  6. Configure Auth Guard
  7. Modify User Model
  8. Create Controller
  9. Add API Routes
  10. Test Endpoints With Postman
  11. 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

Enter fullscreen mode Exit fullscreen mode

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.
Database connection on php my admin

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

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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"

Enter fullscreen mode Exit fullscreen mode

a secret key needs to be generated to handle the token encryption run this command to achieve that

php artisan jwt:secret

Enter fullscreen mode Exit fullscreen mode

This will update your .env file with something like

JWT_SECRET=foobar
Enter fullscreen mode Exit fullscreen mode

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',
        ],

    ],
Enter fullscreen mode Exit fullscreen mode

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 [];
    }

}


Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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',
            ]
        ]);
    }

}

Enter fullscreen mode Exit fullscreen mode

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

});

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

now we are ready to begin testing;
start your postman app
here are the lists of endpoints we will be testing

  1. Register: localhost:8000/api/register. Method: POST
  2. Login: localhost:8000/api/login. Method: POST
  3. Logout: localhost:8000/api/logout. Method: POST
  4. Refresh: localhost:8000/api/refresh. Method:POST
  5. 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
Postman Register User

Login API

Add the email and password to the input field click Send to see the response
Postman Login

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.
Authorization Header

Me API
Me API

Refresh API
Refresh API

Logout
Logout API

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

Top comments (5)

Collapse
 
trczx profile image
Skinny Dev

I've succeed to login, now if I want to create other controller (eg: TodoController), how to check if the token is valid?

Collapse
 
jegjessing profile image
Jarl Gjessing

Ah - just "solved it" replaced the Auth with JWTAuth

Collapse
 
grizmio profile image
grizmio

Where did you do it? Thanks

Collapse
 
jegjessing profile image
Jarl Gjessing

Following the example step by step, resulted in attempt returning a boolean instead of a token :-(

Any ideas?

Collapse
 
juanxodj profile image
Juan Sánchez

It happens the same to me. It returns true.