JWT Authentication in Laravel API

Implementing JWT Authentication in Laravel API

JWT (JSON Web Token) is an effective and widely adopted method for securing APIs using token-based authentication, ensuring that only authenticated users have access to your API endpoints. Unlike traditional session-based authentication, JWT is stateless, meaning it doesn't require server-side session storage, making it perfect for scalable and efficient applications. In this guide, we'll walk you through implementing JWT authentication in a Laravel API, from generating tokens during user login to securing your endpoints by validating these tokens, ultimately strengthening the security of your application's data and resources.

Prerequisites

  • Composer
  • PHP 8.2

Setup project

composer create-project laravel/laravel laravel_api 11.0.3
cd laravel_api
composer require laravel/ui "^4.0"
composer require tymon/jwt-auth

Project structure

├─ .env
├─ app
│  ├─ Http
│  │  ├─ Controllers
│  │  │  └─ Auth
│  │  │     └─ LoginController.php
│  │  └─ Middleware
│  │     └─ Authenticate.php
│  └─ Models
│     └─ User.php
├─ bootstrap
│  └─ app.php
├─ config
│  └─ auth.php
├─ database
│  └─ database.sqlite
├─ resources
│  └─ views
│     ├─ index.html
│     └─ login.html
└─ routes
   ├─ api.php
   └─ web.php

You can download the database.sqlite file here.

Project files

.env

SESSION_DRIVER=file
CACHE_STORE=file
DB_CONNECTION=sqlite
JWT_SECRET=b0WciedNJvFCqFRbB2A1QhZoCDnutAOen5g1FEDO0HsLTwGINp04GXh2OXVpTqQL

This .env file configures the application to use file-based storage for sessions and caching, connects to an SQLite database, and sets a secret key for JSON Web Token (JWT) authentication.

api.php

<?php

use App\Http\Controllers\Auth\LoginController;

Auth::routes();
Route::group(['middleware' => 'auth'], function() {
    Route::get('/user', [ LoginController::class, 'getUser' ]);
});

This api.php file defines API routes for a Laravel application. It enables authentication routes using Auth::routes() and restricts access to the /user endpoint, requiring users to be authenticated. The /user route invokes the getUser method from the LoginController.

web.php

<?php

Route::get('/', function () {
    return view('index');
});

Route::get('/login', function () {
    return view('login');
});

This web.php file defines basic web routes for a Laravel application. The root route (/) loads the index view, while the /login route loads the login view.

Authenticate.php

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    protected function redirectTo($request)
    {
        return abort(401);
    }
}

This Authenticate.php middleware file customizes the authentication handling in a Laravel application. It extends the default authentication middleware and overrides the redirectTo() method to return a 401 Unauthorized error (abort(401)) instead of redirecting to a login page when an unauthenticated request is made.

auth.php

<?php

return [
    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users'
    ],
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users'
        ],
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users'
        ]
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class
        ]
    ],
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60
        ]
    ]
];

This auth.php file sets up authentication settings for a Laravel application. It defines the default guard as api using JWT for token-based authentication. It includes two guards: web for session-based authentication and api for JWT-based authentication.

app.php

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([
            'auth' => \App\Http\Middleware\Authenticate::class
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

This app.php file configures a Laravel application, setting up routes for web, API, console, and health checks, and registering the auth middleware alias to the custom Authenticate middleware.

User.php

<?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;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        return [];
    }
}

This User.php model has been modified to support JWT authentication by implementing the JWTSubject interface. It includes methods getJWTIdentifier() to return the user's primary key and getJWTCustomClaims() to define any additional custom claims for the token.

LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Routing\Controller as BaseController;

class LoginController extends BaseController
{
    use AuthorizesRequests;

    protected function getUser()
    {
        return response()->json([
            'name' => Auth::user()->name
        ]);
    }

    protected function credentials()
    {        
        return [ 'name' => request()->name, 'password' => request()->password ];
    }

    protected function login()
    {
        if (!$token = auth()->attempt($this->credentials(request()))) {
            abort(400);
        }
        return response()->json([
            'token' => $token
        ]);
    }
}

This LoginController.php manages user authentication in a Laravel application, handling user login via JWT by validating credentials and returning a token upon successful authentication, or a 400 error if authentication fails. It also provides a method to retrieve the authenticated user's name.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col mt-5">
                <div class="d-flex justify-content-center">
                    <div class="card p-0">
                        <div class="card-header">
                            <h3>Home</h3>
                        </div>
                        <div class="card-body text-center">
                            <i id="icon" class="fa fa-times-circle fa-5x mt-3 text-danger"></i>
                            <p id="message" class="mt-3">
                                You are currently not logged in.
                                <br/>
                                Redirecting to login page in seconds..
                            </p>
                            <div id="logout" class="col-12 d-none">
                                <button class="btn btn-primary w-100" onclick="logout()">Logout</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script>
        function init() {
            fetch('/api/user', {
                headers: {
                    Authorization: 'Bearer ' + localStorage.getItem('token')
                }
            }).then(response => {
                if (response.ok) {
                    response.json().then(data => {
                        document.getElementById('icon').className = 'fa fa-check-circle fa-5x mt-3 text-success'
                        document.getElementById('message').innerText = 'You are currently logged in as: ' + data.name
                        document.getElementById('logout').classList.remove('d-none')
                    })
                }
                else {
                    if (response.status === 401) {
                        setTimeout(() => {
                            location = '/login'
                        }, 4000)
                    }
                    else {
                        alert(`Error: ${response.status} ${response.statusText}`)
                    }
                }
            })
        }

        function logout() {
            localStorage.removeItem('token')
            location.reload()
        }

        init()
    </script>
</body>

The index.html is a simple web page that provides a user interface for displaying the login status of a user. It uses Bootstrap for styling and Font Awesome for icons. On page load, it checks the user's authentication status by sending a request to the server with a JWT token stored in localStorage. If the user is logged in, it shows a success message with the user's name and a logout button. If not logged in, it shows a message indicating the user is not logged in and redirects them to the login page after a few seconds.

login.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col mt-5">
                <div class="d-flex justify-content-center">
                    <div class="card p-0" style="width: 300px;">
                        <div class="card-header">
                            <h3>Login</h3>
                        </div>
                        <div class="card-body">
                            <i class="fa fa-user-circle fa-5x d-block text-center text-secondary"></i>
                            <form onsubmit="return login()">
                                <div class="row">
                                    <div class="mb-3 col-12">
                                        <label class="form-label" for="user_account_name">User Name</label>
                                        <input id="name" class="form-control form-control" value="admin" required/>
                                    </div>
                                    <div class="mb-3 col-12">
                                        <label class="form-label" for="user_account_password">Password</label>
                                        <input id="password" class="form-control form-control" type="password" value="1234" required/>
                                    </div>
                                    <div class="col-12">
                                        <button class="btn btn-primary w-100">Login</button>
                                    </div>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script>
        function login() {
            fetch('/api/login', {
                method: 'POST',
                body: JSON.stringify({
                    name: document.getElementById('name').value,
                    password: document.getElementById('password').value
                }),
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(response => {
                if (response.ok) {
                    response.json().then(data => {
                        localStorage.setItem('token', data.token)
                        location = '/'
                    })
                }
                else {
                    alert(`Error: ${response.status} ${response.statusText}`)
                }
            })
            return false
        }
    </script>
</body>

The login.html page provides a simple login form where users can input their username and password. It uses Bootstrap for styling and Font Awesome for icons. When the user submits the form, a JavaScript function login() sends a POST request to the /login endpoint with the entered credentials. If the login is successful, the server returns a JWT token, which is stored in localStorage. The page then redirects the user to the home page (/). If the login fails, an error message is displayed.

Run project

php artisan serve

Open the web browser and goto http://localhost:8000
You will find this test page.

home page

Testing

After a few seconds, you will be redirected to the login page.

login page

Press the login button, and you will be logged in to the home page, which will display the logged-in user's name.

logged in

Try refreshing the browser, and you will see that you're still logged in. Then, press the logout button, the JWT token will be removed, and you will be redirected to the login page again.

login page

Conclusion

In conclusion, implementing JWT authentication in a Laravel API provides a secure and efficient way to handle user authentication using token-based authentication. By integrating the Tymon\JWTAuth package, setting up the necessary middleware, and modifying the User model to implement the JWTSubject interface, you can easily authenticate users and protect your API routes. This approach eliminates the need for session management and offers a stateless solution, making it ideal for modern web and mobile applications. With proper configuration and error handling, JWT ensures seamless and scalable user authentication for your Laravel API.

Source code: https://github.com/stackpuz/Example-JWT-Laravel-11

Create a CRUD Web App in Minutes: https://stackpuz.com

Related post