Part 1 - API Authentication using Sanctum

Introduction of the Project

This is the first part of our series where we will be building a notes app in Laravel Framework. We will be making the app in Laravel and ReactJS. I will try to follow the best practices and we will have discussions over its architecture along the way.

Creating a Laravel Project

Let's create a new project

composer create-project laravel/laravel notes

Now cd into your 'notes' directory and run the project.

cd notes
php artisan serve

I have got Laravel 10.2.6.

Authentication using Sanctum

We are using Laravel Sanctum for API authentication. You can also use a Laravel passport but I have never used Sanctum before so prefer using it. The good thing is that Sanctum comes pre-installed with Laravel. So let's begin with implementing a register, login, and logout mechanism.

We will start by creating a controller

php artisan make:controller AuthenticationController

It will create AuthenticationController.php in app\Http\Controllers. Let's create the functions.

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Exception;

class AuthenticationController extends Controller {
    public function register (RegisterUserRequest $request): JsonResponse {
        throw new Exception ("Not implemented", 501);
    }

    public function login (LoginUserRequest $request): JsonResponse {
        throw new Exception ("Not implemented", 501);
    }

    public function logout (Request $request): JsonResponse {
        throw new Exception ("Not implemented", 501);
    }
}

Oh, we are submitting a form here for register and login requests so we need to validate requests before anything else. Let's create the requests.

php artisan make:request RegisterUserRequest
php artisan make:request LoginUserRequest

Now we will import these at the top of the AuthenticationController.php

use App\Http\Requests\LoginUserRequest;
use App\Http\Requests\RegisterUserRequest;
// remaining code...

Now it is time to implement the user registration. Edit the App\Http\Requests\RegisterUserRequest.php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterUserRequest extends FormRequest {
    public function authroize (): bool {
        // everyone is able to register right?
        return true;
    }

    public function rules (): array {
        return [
            "name" => "required | string | max:50 | min:3",
            "email" => "required | email | max:50 | min:5",
            "password" => "required | max:50 | min: 8"
        ];
    }
}

Now we can implement the register function in AuthenticationController.php

// ... imports
use App\Models\User;
use App\Http\Requests\RegisterUserRequest;

public function register (RegisterUserRequest $request): JsonReponse {
    $user = User::create ($request->all());

    $token = $user->createToken('token');

    return response()->json([
        'user' => $user,
        'token' => $token->plainTextToken
    ]);
}

Now we need to add the route only and then we are ready to register users. Open routes/api.php and add a route for register.

use App\Http\Controllers\AuthenticationController;

// Auth Routes
Route::post('register', [AuthenticationController::class, 'register']);

Setting up the database

The above flow is complete but we cannot really create any user yet because we have not configured the database. Since this is a tutorial, let's use sqlite. Open your .env file and change the database configuration to

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

Now let's migrate the database. It will ask you to confirm creating a new database so do allow it by answering yes.

php artisan migrate

Now run the server if not already running using php artisan serve and make a request to localhost:8000/api/register

And it works! But you know, I am not comfortable with the code. We decided that we would follow best practices. Let's implement login and logout methods and then discuss this further.

Implementing Login

Let's implement login method. We know that Auth Facade in Laravel provides attempt method that takes the credentials and returns true if the credentials are matched. So let's use it for login.

First, edit the LoginUserRequest.php as follows

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class LoginUserRequest extends FormRequest
{
    public function authorize(): bool {
        // anyone can login, right?
        return true;
    }

    public function rules(): array {
        return [
            "email" => "required | email | max:50 | min:5",
            "password" => "required | max:50 | min: 8"
        ];
    }
}

Now let's edit the login method in AuthenticationController

use App\Http\Requests\LoginUserRequest;

public function login (LoginUserRequest $request): JsonResponse {
    if (Auth::attempt ($request->all())) {
        $user = User::where('email', $request->email)->first();

        $token = $user->createToken('token');

        return response()->json([
            'user' => $user,
            'token' => $token->plainTextToken
        ]);
    } else {
        return response()->json([
            'message' => 'Invalid credentials'
        ], 401);
    }
}

Now let's add a route for login in routes/api.php after register

Route::post('login', [AuthenticationController::class, 'login');

Run the server if not already running, and send a post request to login

Yeah! The code is working as expected. Only the logout method and then we are done. So let's implement logout and complete the authentication stuff.

Implementing Logout

Let's implement logout method in AuthenticationController

public function logout (Request $request): JsonResponse {
    $request->user()->currentAccessToken()->delete();

    return response()->json([]);
}

Define the route in routes/api.php. But this time we need an authenticated user, since only those who have logged in can logout, right? So, let's make a middleware group in the file to include routes that require authentication first.

Route::middleware('auth:sanctum')->group(function () {
    Route::post('logout', [AuthenticationController::class, 'logout']);
});

Now you can log out.

So that's it. Our authentication module is ready. Let's dive deeper and discuss what's wrong with the code in the next part. Stay tuned till then.