Laravel API protection with generate access token

Protecting your API with your own custom access token is a really easy way to protect API. All you need to do is to generate your own access token with a random string and store it with the user's id that login so we know which access tokens belong to which users. Think as if you already have the User model and users table in the database with the fields of id, name, email, password, created_at, and updated_at which store the user data for use with login, and logout to generate the token.

Step 1 - Create migration file and model for access token table

The first step we need to create a migration file to create the access_tokens table by running php artisan migrate and model so that we can create, and delete the access token data.

php artisan make:migration create_access_tokens_table

In the database/migrations/create_access_tokens_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAccessTokensTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('access_tokens', function (Blueprint $table) {
            $table->id();
            $table->string('access_token');
            $table->unsignedInteger('user_id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('access_tokens');
    }
}


php artisan make:model AccessToken

In the app/Models/AccessToken.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class AccessToken extends Model
{
    protected $table = 'access_tokens';
    protected $fillable = ['access_token', 'user_id'];
}

Step 2 - Create AuthController for handling login, logout

AuthController login function will generate an access token with the length of 255 when the user login with email, and password and store the access token in the access_tokens table with access_token and user_id fields. 

The logout function logic will delete the access token from the access_tokens tables.

php artisan make:controller AuthController

In the app/Http/Controllers/AuthController.php

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Models\AccessToken;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    public function login(Request $request)
    {
        try {
            $user = User::where('email', $request->email)->first();
            if (!$user) {
                return response()->json([ 'message' => 'Invalid credential' ]);
            }

            $check = Hash::check($request->password, $user->password);
            if (!$check) {
                return response()->json([ 'message' => 'Invalid credential' ]);
            }
            
            $accessToken = AccessToken::updateOrCreate(
                [ 'user_id' => $user->id ],
                [ 'access_token' => Str::random(255) ]
            );
            return response()->json([ 'access_token' =>  $accessToken->access_token ]);
        } catch (\Throwable $th) {
            throw $th;
        }
    }

    public function logout(Request $request)
    {
        try {
            $accessToken = AccessToken::where('access_token', $request->access_token)->first();
            if ($accessToken) {
                $accessToken->delete();
                return response()->json([ 'success' => true ]);
            }
            
            return response()->json([ 'success' => false ]); 
        } catch (\Throwable $th) {
            throw $th;
        }
    }
}

Step 3 - Create and register middleware to protect API with the access token

This step is to create AccessTokenMiddleware to check if the API request has a header of Authorization with a valid access token value we allow to process further. When the access token null or invalid we respond with the Unauthorized with status code 401 Unauthorized.

php artisan make:middleware AccessTokenMiddleware

In the app/Http/Middleware/AccessTokenMiddleware.php

<?php

namespace App\Http\Middleware;

use App\Models\AccessToken;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class AccessTokenMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        $authorization = $request->header('Authorization');
        $accessToken = AccessToken::where('access_token', $authorization)->first();
        if (!$accessToken) {
            return response()->json([ 'message' => 'Unauthorized' ], Response::HTTP_UNAUTHORIZED); 
        }
        return $next($request); 
    }
}

Register your middleware in the Kernel to give it a name we give it a name auth.api to use so that we can use it in the route.

In the app/Http/Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array<int, class-string|string>
     */
    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,\App\Http\Middleware\TrustProxies::class,\Fruitcake\Cors\HandleCors::class,\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array<string, array<int, class-string|string>>
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,\Illuminate\View\Middleware\ShareErrorsFromSession::class,\App\Http\Middleware\VerifyCsrfToken::class,\Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,'throttle:api',\Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array<string, class-string|string>
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'auth.api' => \App\Http\Middleware\AccessTokenMiddleware::class
    ];
}

Step 4 - Register our login, logout to the API route

Simple request

Request: {{ _.url }}/api/login, POST, 

JSON:

{
 "email": "",
 "password": ""
}

Request: {{ _.url }}/api/logout, POST,

JSON:

{
 "access_token": ""
}


Route::post('login', [AuthController::class, 'login']);
Route::post('logout', [AuthController::class, 'logout']);

Step 5 - Protect your API in the route

Their many different ways to protect the controller in Laravel, you can protect it by constructing the middleware class in your controller or by using the way I’m showing which is wrapping the middleware over the API routes that you want to protect. This way before the request hit the controller it will go to the middleware first. As we check against auth.api so it will go through AccessTokenMiddleware and run the handling logic.

In the routes/api.php

Route::group(['middleware' => 'auth.api'], function () {
   Route::apiResource('article', ArticleAdminController::class);
   Route::apiResource('category', CategoryAdminController::class);
   Route::apiResource('tag', TagAdminController::class);
});

Conclusion

After all these steps you will have a login, and logout function logic, as well as the middleware to protect your API route with your own, generate access_token.

Do you have any idea how we can get login user information with this custom access token?