Laravel, logowanie w rest api
W tym wpisie poruszę takie zagadnienia jak testowanie i implementacja logowania w api laravel wykorzystując tokeny JWT.
By nieco przyspieszyć wykonywanie testów skonfiguruję środowisko tak, by framework do testowania (PHPUnit) używał memory: database.
Dodaję konfigurację bazy testowej w pliku config/database.php
'connections' => [
'testing' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
...
]
Zmieniam konfig pliku ./phpunit.xml w sekcji php:
<env name="DB_CONNECTION" value="testing"/>
Ok, środowisko gotowe, można przystąpić do napisania pierwszego testu:
php artisan make:test Auth/LoginTest
<?php
namespace Tests\Feature\Auth;
use App\User;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class LoginTest extends TestCase
{
use RefreshDatabase, WithFaker;
/** @test */
public function user_can_log_in()
{
$this->withoutExceptionHandling();
factory(User::class)->create([
'email' => 'email@example.com',
'password' => Hash::make('secret')
]);
$response = $this->json('POST', '/api/login', [
'email' => 'email@example.com',
'password' => 'secret'
]);
$response->assertJsonStructure([
'access_token',
'token_type',
'expires_in'
]);
}
}
Uruchamiam test:
vendor/bin/phpunit
Pierwszy error:
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: POST http://localhost/api/login
Tworzę api route login:
Route::post('/login', 'Api\Auth\LoginController@login');
Kolejny error:
ReflectionException: Class App\Http\Controllers\Api\Auth\LoginController does not exist
Tworzę wspomniany LoginController:
php artisan make:controller Api/Auth/LoginController
Jak można się spodziewać metoda login nie istnieje:
BadMethodCallException: Method App\Http\Controllers\Api\Auth\LoginController::login does not exist.
Więc ją tworzę i cały LoginController wygląda następująco:
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Controller;
class LoginController extends Controller
{
/**
* Get a JWT via given credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function login()
{
$credentials = request(['email', 'password']);
if (!$token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
/**
* Get the token array structure.
*
* @param string $token
*
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth()->factory()->getTTL() * 60
]);
}
}
Po tych zmianach test przechodzi:
vendor/bin/phpunit --filter=user_can_log_in OK (1 tests, 3 assertions)
Utworzę teraz test i funkcjonalność zapewniającą, że użytkownik, który nie zweryfikował swojego adresu email nie będzie mógł się zalogować.
/** @test */
public function user_cannot_log_in_without_verified_email()
{
$this->withoutExceptionHandling();
factory(User::class)->create([
'email' => 'email@example.com',
'password' => Hash::make('secret'),
'email_verified_at' => null
]);
$response = $this->json('POST', '/api/login', [
'email' => 'email@example.com',
'password' => 'secret'
]);
$response->assertJson(['error' => 'Email Not Verified']);
$response->assertStatus(401);
}
Test oczywiście nie przechodzi:
vendor/bin/phpunit --filter=user_cannot_log_in_without_verified_email FAILURES! Tests: 1, Assertions: 1, Failures: 1.
Laravel 5.8 domyślnie wprowadził w modelu App\User konrakt Illuminate\Contracts\Auth\MustVerifyEmail, dzięki czemu metodka potwierdzająca weryfikację adresu email jest już zaimplementowana, wystarczy jej użyć. Wcześniej trzeba było samemu napisać coś zapewniającego tą funkcjonalność. W takim razie w metodzie login mojego LoginControllera dodaję
if (!auth()->user()->hasVerifiedEmail()) {
return response()->json(['error' => 'Email Not Verified'], 401);
}
Po tej zmianie test przechodzi:
vendor/bin/phpunit --filter=user_cannot_log_in_without_verified_email OK (1 test, 2 assertions)
W kolejnym wpisie opiszę rejestrację użytkownika wraz z wysłaniem maila proszącego o weryfikację adresu email.