DEV Community

dgloriaweb
dgloriaweb

Posted on

Laravel 10 API application with necessary functions pt 3.

Environment: Windows 10

Necessary prequisites (knowledge): VS Code, Composer, Terminal, Git bash, GitHub, Postman, MarkDown, XAMPP (also serves my MySql database), environmental variables (or .env), Passport, TDD, Pest

If you require further details, please feel free to add in comments.


User authentication, login, register

I am using Laravel Passport (https://laravel.com/docs/10.x/passport) for user authentication.
composer require laravel/passport
then run the migrations
php artisan migrate (in my case it didn't do anything)
Then install passport:
php artisan passport:install
This generated the necessary tables in the database, that's connected to Oauth. Explore these. I have 10 tables now.
In the User.php Model file, change the use Laravel\Sanctum\HasApiTokens; to use Laravel\Passport\HasApiTokens;
(I've noticed that my oauth_clients table was actually empty, so I had to re-run the install and re-create these. Probably because the default RefreshDatabase in the test deletes everything that's in the database, so I've changed this)

Routing

This is where you tell the app, where the incoming API requests should be redirected to.

TDD to create register API endpoint

Run this to install Pest instead of phpunit
composer remove phpunit/phpunit
composer require pestphp/pest --dev --with-all-dependencies
initialise
./vendor/bin/pest --init
to run tests type:
./vendor/bin/pest
Then create the test file: tests/Feature/UserManagementTest.php

<?php

use Illuminate\Foundation\Testing\DatabaseTransactions;

uses(DatabaseTransactions::class);

it('allows a user to register', function () {
    $response = $this->postJson('/api/register', [
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'password' => 'password',
        'password_confirmation' => 'password',
    ]);

    $response->assertStatus(200);
});
Enter fullscreen mode Exit fullscreen mode

We create the route in the routes/api.php file (I've deleted the sanctum route that was there):

Route::group(['middleware' => ['cors', 'json.response']], function () {
    // public routes
    Route::post('/register', 'App\Http\Controllers\Auth\ApiAuthController@register')->name('register.api');
});

Enter fullscreen mode Exit fullscreen mode

Run the test again: "Target class [cors] does not exist.". Run this to add Cors middleware:
php artisan make:middleware Cors
Replace the handler:

 return $next($request)
            ->header('Access-Control-Allow-Origin', '*')
            ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
            ->header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, X-Token-Auth, Authorization');
Enter fullscreen mode Exit fullscreen mode

php artisan make:middleware ForceJsonResponse
Replace the handler:

 $request->headers->set('Accept', 'application/json');
        return $next($request);
Enter fullscreen mode Exit fullscreen mode

then add these to the kernel.php file,

Image description

Image description

Then run the test again: "Expected response status code [200] but received 500."

We don't have the controller yet, so create an Auth folder in Http/Controllers and add ApiAuthController.php file:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Auth\Events\Registered;



class ApiAuthController extends Controller
{
    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
        if ($validator->fails()) {
            return response(['errors' => $validator->errors()->all()], 422);
        }
        $request['password'] = Hash::make($request['password']);
        $request['remember_token'] = Str::random(10);
        $user = User::create($request->toArray());

        try {
            $user->save();
            event(new Registered($user));

        } catch (Exception $e) {
            report($e);
            return false;
        }
    }

    public function login(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'email' => 'required|string|email|max:255',
            'password' => 'required|string|min:6|confirmed',
        ]);
        if ($validator->fails()) {
            return response(['errors' => $validator->errors()->all()], 422);
        }
        $user = User::where('email', $request->email)->first();
        if ($user) {
            if (Hash::check($request->password, $user->password)) {
                $token = $user->createToken('Laravel Password Grant Client')->accessToken;
                return response([
                    'token' => $token,
                    'userId' => $user->id,
                    'successMessage' => "User successfully logged in"
                ], 200);
            } else {
                return response(['errors' => "Password mismatch"], 422);
            }
        } else {
            return response(['errors' => "User doesn't exist"], 422);
        }
    }

    public function logout(Request $request)
    {
        $token = $request->user()->token();
        $token->revoke();
        return response(['successMessage' => 'You have been successfully logged out!'], 200);
    }
    public function verifyEmail($id, $hash)
    {
        $user = User::find($id);
        if (!$user) {
            return response()->json(['message' => 'Invalid user'], 404);
        }
        if (!hash_equals($hash, sha1($user->getEmailForVerification()))) {
            return response()->json(['message' => 'Invalid verification link'], 401);
        }
        if ($user->hasVerifiedEmail()) {
            return redirect('/')->with('message', 'Email already verified');
        }
        $user->markEmailAsVerified();
        return redirect('/');
    }
}

Enter fullscreen mode Exit fullscreen mode

Now the test should pass. If you run the request in postman, provide the body as in the test, you should be able to register. Make sure that php artisan serve is running.

Login endpoint

Add this to UserManagementTest.php

it('allows a user to login', function () {
    // Create a user in the database
    $user = User::create([
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'password' => Hash::make('password'),
        'password_confirmation' => Hash::make('password'),
    ]);

    // Attempt to log in with the created user's credentials
    $response = $this->postJson('/api/login', [
        'email' => 'john@example.com',
        'password' => 'password',
        'password_confirmation' => 'password',
    ]);

    // Assert the response status and structure
    $response->assertStatus(200)
             ->assertJsonStructure(['token']);
});

Enter fullscreen mode Exit fullscreen mode

We need to add the router definition to fix the 404 error:

 Route::post('/login', 'App\Http\Controllers\Auth\ApiAuthController@login')->name('login.api');
Enter fullscreen mode Exit fullscreen mode

Top comments (0)