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);
});
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');
});
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');
php artisan make:middleware ForceJsonResponse
Replace the handler:
$request->headers->set('Accept', 'application/json');
return $next($request);
then add these to the kernel.php file,
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('/');
}
}
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']);
});
We need to add the router definition to fix the 404 error:
Route::post('/login', 'App\Http\Controllers\Auth\ApiAuthController@login')->name('login.api');
Top comments (0)