DEV Community

Fajar Windhu Zulfikar
Fajar Windhu Zulfikar

Posted on • Originally published at fajarwz.com on

Laravel Rest API Authentication Using JWT Tutorial

Token illustration

If we work with a microservice architecture, non-monolith project, then we will see our project have it's backend and frontend separated. There will be a team for both Frontend and Backend.

Some apps need authentication, maybe the app needs to collect user's email for better engagement, or it needs an id so it is obvious which data belongs to which user, etc.

There are some method to authenticate user but here I will share about token-based authentication using JWT (JSON Web Token). We will use PHP-Open-Source-Saver/jwt-auth. Here is the step by step about how to build it.

Laravel Installation

First we will create a fresh install of Laravel app. Type this from your terminal

laravel new blog-api-docs-l5-swagger
Enter fullscreen mode Exit fullscreen mode

Install Laravel

After the installation finished, open the project with your IDE or text editor and then install the Laravel packages needed.

PHP-Open-Source-Saver/jwt-auth Installation

Install the package for our app's token-based authentication, JWT token.

Step 1: Install The Package

Install the package with following command

composer require php-open-source-saver/jwt-auth
Enter fullscreen mode Exit fullscreen mode

Step 2: Publish The Config File

Run this in your terminal to publish the package config file

php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
Enter fullscreen mode Exit fullscreen mode

Now you should have config/jwt.php file in your project for the package configuration.

Step 3: Generate Secret Key

Run the following command to generate secret key for the jwt.

php artisan jwt:secret
Enter fullscreen mode Exit fullscreen mode

Step 4: Configure config/auth.php

Since we only use the Laravel app for API only, we can change the authentication default to api

    'defaults' => [
        'guard' => 'api',   // change this to api
        'passwords' => 'users',
    ],
Enter fullscreen mode Exit fullscreen mode

Next, add the api we already defined at the top to the guards

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

    // add below code. Here we insert additional guards named api, which the driver is jwt and the provider is users (the users provider already defined below this code in the original file, pointing to User model)
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],
Enter fullscreen mode Exit fullscreen mode

Step 5: Add Blacklist Exception

Add this to our .env so we can logout our users.

JWT_SHOW_BLACKLIST_EXCEPTION=true
Enter fullscreen mode Exit fullscreen mode

Ok that's it for the PHP-Open-Source-Saver/jwt-auth installation and it's configuration.

Configure the .env file

Point your Laravel app to the correct database. If you set the DB_DATABASE to laravel_api_jwt then don't forget to create a MySQL database named laravel_api_jwt as well.

DB_DATABASE=laravel_api_jwt
Enter fullscreen mode Exit fullscreen mode

Migrate the Database Migration

No need to edit the default migration if the users table migration already contained email, password, and email. Use the following command to migrate:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Preparing User Model

We should integrate our User Model with JWT Auth like so:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject;    // add this 

class User extends Authenticatable implements JWTSubject // implement the 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',
    ];

    // add two methods below

    /**
     * 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

Make sure you implement the JWTSubject and add the two methods from JWT Auth.

Preparing Controllers

We will create examples of auth-needed and no-auth-needed endpoints.

Step 1: Create the Auth Controller

We will need to generate the controller with the following command.

php artisan make:controller Api/AuthController
Enter fullscreen mode Exit fullscreen mode

You will see it generated inside app/Http/Controllers/Api folder. Once it's created let's fill it with the following codes.

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;

class AuthController extends Controller
{
    public function __construct(User $user)
    {
        // model as dependency injection
        $this->user = $user;
    }

    public function register(Request $request)
    {
        // validate the incoming request
        // set every field as required
        // set email field so it only accept the valid email format

        $this->validate($request, [
            'name' => 'required|string|min:2|max:255',
            'email' => 'required|string|email:rfc,dns|max:255|unique:users',
            'password' => 'required|string|min:6|max:255',
        ]);

        // if the request valid, create user

        $user = $this->user::create([
            'name' => $request['name'],
            'email' => $request['email'],
            'password' => bcrypt($request['password']),
        ]);

        // login the user immediately and generate the token
        $token = auth()->login($user);

        // return the response as json 
        return response()->json([
            'meta' => [
                'code' => 200,
                'status' => 'success',
                'message' => 'User created successfully!',
            ],
            'data' => [
                'user' => $user,
                'access_token' => [
                    'token' => $token,
                    'type' => 'Bearer',
                    'expires_in' => auth()->factory()->getTTL() * 60,    // get token expires in seconds
                ],
            ],
        ]);
    }

    public function login(Request $request)
    {
        $this->validate($request, [
            'email' => 'required|string',
            'password' => 'required|string',
        ]);

        // attempt a login (validate the credentials provided)
        $token = auth()->attempt([
            'email' => $request->email,
            'password' => $request->password,
        ]);

        // if token successfully generated then display success response
        // if attempt failed then "unauthenticated" will be returned automatically
        if ($token)
        {
            return response()->json([
                'meta' => [
                    'code' => 200,
                    'status' => 'success',
                    'message' => 'Quote fetched successfully.',
                ],
                'data' => [
                    'user' => auth()->user(),
                    'access_token' => [
                        'token' => $token,
                        'type' => 'Bearer',
                        'expires_in' => auth()->factory()->getTTL() * 60,
                    ],
                ],
            ]);
        }
    }

    public function logout()
    {
        // get token
        $token = JWTAuth::getToken();

        // invalidate token
        $invalidate = JWTAuth::invalidate($token);

        if($invalidate) {
            return response()->json([
                'meta' => [
                    'code' => 200,
                    'status' => 'success',
                    'message' => 'Successfully logged out',
                ],
                'data' => [],
            ]);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Step 2: Create the User Controller

Generate the controller with the following command.

php artisan make:controller Api/UserController
Enter fullscreen mode Exit fullscreen mode

This controller will only contain a single method me to retrieve authenticated user data. Insert the following codes in the UserController located in the app\Http\Controllers\Api folder.

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;  // add the User model

class UserController extends Controller
{
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function me() 
    {
        // use auth()->user() to get authenticated user data

        return response()->json([
            'meta' => [
                'code' => 200,
                'status' => 'success',
                'message' => 'User fetched successfully!',
            ],
            'data' => [
                'user' => auth()->user(),
            ],
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Configure Routing

Register all the action to our API route in routes/api.php

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\UserController;
use App\Http\Controllers\Api\AuthController;

/*
|--------------------------------------------------------------------------
| 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!
|
*/

// Public accessible API
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

// Authenticated only API
// We use auth api here as a middleware so only authenticated user who can access the endpoint
// We use group so we can apply middleware auth api to all the routes within the group
Route::middleware('auth:api')->group(function() {
    Route::get('/me', [UserController::class, 'me']);
    Route::post('/logout', [AuthController::class, 'logout']);
});
Enter fullscreen mode Exit fullscreen mode

That's it, next we can test the API.

Serve

We can run the API using artisan serve

php artisan serve
Enter fullscreen mode Exit fullscreen mode

Now our API can be accessed at the 127.0.0.1:8000/api.

Test the API

I use Insomnia to test it.

Register API

Register API

Login API

Login API

User data API

To access our authentication protected route, we need to insert token in the authorization header with Bearer prefix. In insomnia we can do that with inserting the token in the Bearer tab

Me API

Logout API

Logout API

Conclusion

That's it. We have successfully build a simple API with authentication. Here we use PHP-Open-Source-Saver/jwt-auth package, integrate it with our model and guard, then create the endpoints.

A repo for this example case can be found here fajarwz/blog-laravel-api-jwt.

Further Reading

Laravel JWT Auth

Top comments (0)