Secure Rest API with JWT Authentication in Laravel 8x

Published on September 28, 2020 2 min read

HIRE US 🖐
We're available to do freelance project. Take a look at our services!

SON Web Tokens are an open, industry-standard RFC 7519 method for representing claims securely between two parties. In this article, I’m going to show how to secure Laravel APIs with JWT. I’m testing on Laravel 8.6.0.

Table of Contents

  1. Install Laravel and Basic Configurations
  2. Install JWT Package
  3. Modify User Model
  4. Config Auth Guard
  5. Create Controller
  6. Define API Routes
  7. Test API

Install Laravel and Basic Configurations

Each Laravel project needs this thing. That’s why I have written an article on this topic. Please see this part from here: Install Laravel and Basic Configurations.

After completing basic configuration, run the migration:

php artisan migrate

Install JWT Package

We’ll use tymon/jwt-auth package. Run this composer command to install this package:

composer require tymon/jwt-auth

You need to publish the config file for JWT using the following command:

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

JWT tokens will be signed with an encryption key. Run this command to generate key:

php artisan jwt:secret

The key will be store in .env file like:

JWT_SECRET=secret-key

Modify User Model

We need to define two methods in app/Models/User.php file. The methods are getJWTIdentifier() and getJWTCustomClaims(). The user model needs to implement JWTSubject.

User.php
<?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 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',
    ];

    /**
     * Get JWT identifier.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}

Config Auth Guard

We need to set JWT auth guard as API guard in config/auth.php file.

config/auth.php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
        'hash' => false,
    ],
],

Create Controller

Let’s create a controller named AuthController:

 php artisan make:controller AuthController

Now open the controller from app\Http\Controllers and paste this code:

AuthController.php
<?php

namespace App\Http\Controllers;

use Validator;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login', 'register']]);
    }

    /**
     * Register a User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|between:2,100',
            'email' => 'required|string|email|max:100|unique:users',
            'password' => 'required|string|confirmed|min:6',
        ]);

        if ($validator->fails()) {
            return response()->json($validator->errors()->toJson(), 400);
        }

        $user = User::create(array_merge(
            $validator->validated(),
            ['password' => bcrypt($request->password)]
        ));

        return response()->json([
            'message' => 'User successfully registered',
            'user' => $user
        ], 201);
    }


    /**
     * Get a JWT token via given credentials.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'email' => 'required|email',
            'password' => 'required|string|min:6',
        ]);

        if ($validator->fails()) {
            return response()->json($validator->errors(), 422);
        }

        if (!$token = $this->guard()->attempt($validator->validated())) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function profile()
    {
        return response()->json($this->guard()->user());
    }

    /**
     * Log the user out (Invalidate the token)
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        $this->guard()->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * Refresh a token.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken($this->guard()->refresh());
    }

    /**
     * Get the token array structure.
     *
     * @param string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => $this->guard()->factory()->getTTL() * 60
        ]);
    }

    /**
     * Get the guard to be used during authentication.
     *
     * @return \Illuminate\Contracts\Auth\Guard
     */
    public function guard()
    {
        return Auth::guard('api');
    }
}

I’ve set some simple functions in the controller.

Define API Routes

Open routes/api.php file and define these routes:

api.php
<?php

use App\Http\Controllers\AuthController;
use Illuminate\Support\Facades\Route;

Route::group(['prefix' => 'auth', 'middleware' => 'api'], function () {
    Route::post('register', [AuthController::class, 'register']);
    Route::post('login', [AuthController::class, 'login']);
    Route::post('refresh', [AuthController::class, 'refresh']);
    Route::get('profile', [AuthController::class, 'profile']);
    Route::post('logout', [AuthController::class, 'logout']);
});

Test API

We’ve finished all tasks. Let’s open Postman and start testing our API.

Register:

Login: We need to pass the token as a header field "Authorization: Bearer Token".

Refresh Token:

Profile Data:

Logout:

The tutorial is over. You can download this project from GitHub. Thank you. 🙂

Author

Hey, I'm Md Obydullah. I build open-source projects and write on Laravel, Linux server, modern JavaScript and more on web development.

Follow

2 Replies to “Secure Rest API with JWT Authentication in Laravel 8x”

  1. Hi ! This is a very good post! I did everything you tell us to do, but i’m having some problems when i do not pass my token… For exemple, when i try to use ‘profile’ endpoint without a token or with an invalid token, he thrown me an error. Route [login] not defined. Do you know what i should do to correct this problem ?

Leave a Reply

Your email address will not be published. Required fields are marked *