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.
Testing
After a few seconds, you will be redirected to the 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.
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.
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
Top comments (0)