The majority of projects built with Laravel are traditional web applications where HTML is rendered from the server-side using blade components. However, did you know you could also build a robust REST API with Laravel? It’s relatively simple to build one and in this tutorial, we’ll show you how to build a Laravel REST API using JWT authentication (JSON Web Tokens).

What Is JWT and Why Do We Need It?

JWT stands for JSON Web Token and it is used to authenticate incoming requests to our application. To understand why we need JWT in a REST API, we first need to understand how authentication works in a traditional web application.

In a traditional web application, both the frontend and backend are tightly coupled. That is to say; they are on the same server. When a user logs in with their username and password, a session variable is created and sent with the response back to the browser. This session is then stored in the browser as a cookie and is used for future requests to the Laravel server. On the other hand, a REST backend is loosely coupled to a frontend. The frontend can be a different server or can even be a mobile device. In this case, a session mechanism wouldn’t work. So, we need to use JWT for authentication here and we will have to send the JWT token with every API request.

Now that we understand why we are using JWT, let’s start building our application. We’ll start with a fresh installation of Laravel 7 and show you how to build a Laravel 7 REST API using JWT authentication.

Setting Up a Fresh Laravel 7 Project

Install a new Laravel project using Composer’s create-project command:

composer create-project --prefer-dist laravel/laravel laravel7-rest-api-with-jwt

The command above creates a new Laravel project and pulls in all the required dependencies along with it.

Next, set up the database. I created a MySQL database named laravel7-rest-api-with-jwt and added the database details to the .env file.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel7-rest-api-with-jwt
DB_USERNAME=root
DB_PASSWORD=

Finally, we complete the setup process by running our migrations, which creates the default user-related tables in our database.

php artisan migrate

To verify if the setup works properly, start the webserver using the following command and visit http://127.0.0.1:8000.

php artisan serve

You should see a web page come up similar to this:

Set up JWT Authentication Package

We will be using a famous package called jwt-auth to implement token-based authentication in our Laravel 7 project. This package has over 9000 stars on GitHub and the package makes it very easy to create and issue tokens to our users.

Let’s install the package via Composer:

composer require tymon/jwt-auth

After that, add the service provider to the Providers array in your app.php in the config directory:

'providers' => [
     ...
    /*
     * Package Service Providers...
     */

    'Tymon\JWTAuth\Providers\LaravelServiceProvider'


    /*
     * Application Service Providers...
     */
     ...

],

Next, also in the app.config file, add the JWTAuth and JWTFactory facades to the aliases array.

'aliases' => [
    ...
    'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth',
    'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory'
],

After that, we publish the package’s config using the following command:

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

Finally, let’s generate a secret key that this package will use to encrypt our tokens:

php artisan jwt:secret

The above command generates an encryption key and sets it in our .env with something like JWT_SECRET=keystring.

Prepare User Model for Authentication

We will be using the existing User model for authentication, therefore, we need to integrate our user model with the jwt-auth package. To do that, we’ll implement the Tymon\JWTAuth\Contracts\JWTSubject contract on the user model and define the two required methods, getJWTIdentifier() and getJWTCustomClaims() .

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;

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

Configure the Auth Guard

Now we need to have Laravel always use jwt-auth for authentication instead of the traditional session driver.

Set the default guard to api and the API guard’s driver to jwt in app/auth.config like so:

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

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

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

Define API routes

Now that we’ve installed the package, let’s get to defining the REST API routes for our application.

In Laravel, you should define your API routes in routes/api.php and not on web.php. We will define authentication-related routes here.

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::group([
    'middleware' => 'api',
    'prefix' => 'auth'

], function ($router) {

    Route::post('register', 'JWTAuthController@register');
    Route::post('login', 'JWTAuthController@login');
    Route::post('logout', 'JWTAuthController@logout');
    Route::post('refresh', 'JWTAuthController@refresh');
    Route::get('profile', 'JWTAuthController@profile');

});

Note: all the routes defined in routes/api.php will be prefixed with api/ by Laravel and auth routes are prefixed with auth. So our /login route is actually /api/auth/login and the same holds for all the other routes.

Define Authentication Logic

Now that we’ve defined the routes, it’s time to define the respective controller methods for all those routes. Let’s create a controller to house all our authentication logic:

 php artisan make:controller JWTAuthController

Next, define all the required methods:

<?php

namespace App\Http\Controllers;

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

class JWTAuthController 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|between:2,100',
            'email' => 'required|email|unique:users|max:50',
            'password' => 'required|confirmed|string|min:6',
        ]);

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

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

    /**
     * Get a JWT via given credentials.
     *
     * @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 = auth()->attempt($validator->validated())) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

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

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

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

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

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

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

Explanation

  1. Register: This method is used to register a user and is invoked on /api/auth/register route. This method also takes name, email, and password as an input and creates a new user from it.
  2. Login: This method is invoked when the user hits the /api/auth/login endpoint. We check if the provided email and password are correct and return a JSON token in the response. You should use this token for any subsequent requests.
  3. Profile: This method returns the logged-in user’s details. You need to pass your token in the header field to authenticate this request. I’ll be showing you how to do it in the next section.
  4. Refresh Token: It is a good practice to have short-lived tokens. The tokens are only valid for a short period of time and the user needs to get a new token by sending the old one before it expires. Otherwise, the user would have to login again, which will invalidate the old token.
  5. Logout: This API will invalidate the passed token.

Let’s Test our REST API

We’re done building our API! Let’s test to see if it works. Note that we pass the token as a header field "Authorization:Bearer token" for Profile, Refresh Token, and Logout APIs.

I have tested the API using Postman and here are the results.

Register (/api/auth/register)

register_user_laravel7_api

Login (/api/auth/login)

login_user_laravel7_api

Profile (/api/auth/profile)

profile_user_laravel7_api

Refresh Token (/api/auth/refresh)

refresh_token_laravel7_api

Logout (/api/auth/logout)

logout_user_laravel7_api

That’s it! This is how you can build a simple REST API with basic authentication. If you have any questions, please share it in the comments below! Also, the complete source code for the project built in this tutorial is on my GitHub.

Now that you’ve implemented a basic auth, you might be interested in knowing how to implement different user roles and permissions. You can also check out our other articles in the Laravel section!