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.

You may also like...