CQRS and eventsourcing with API Platform II

February 2, 2019

Here comes Part II of the tutorial.
Part I was about Setup the API Platform and CLI tools.

You can find the sourcecode in the branch part-2 and all changes.

Part II: Setup user management and authentication

Implementing User-Management with privileges

Add a User Entity

As API Platform Docs strongly encourage not to use FOSUserbundle, we will make use of the doctrine user provider.

We create a new User-Entity implementing Symfony\Component\Security\Core\User\UserInterface.

  • The Greeting-Class can be removed

In security we add the encoders and providers sections.

We will user Uuid’s as user-ids, so we need to install ramsey/uuid-doctrine-package.

$ docker-compose exec php composer require ramsey/uuid-doctrine

After this update the database-schema:

$ docker-compose exec php bin/console doctrine:schema:update --force

Opening the Swagger-Frontend you can now play around adding and deleting users.

Swagger

Let’s create a user with a request:

$ http --json POST localhost:8080/users username=rabbl password=test1234
HTTP/1.1 201 Created
...

Get the list of users:

$ http --json localhost:8080/users
HTTP/1.1 200 OK
...

Delete the user by id:

$ http --json DELETE localhost:8080/users/72600004-51ae-4b0e-88f4-f9ee6bc35abd
HTTP/1.1 204 No Content
...

Create a Command to create Users on CLI

Now you can create users via CLI:

$ docker-compose exec php bin/console app:create-user admin admin_pw ROLE_ADMIN
$ docker-compose exec php bin/console app:create-user user1 user_pw

Implement JWT-Authentication

You can find a very straightforward installation guide in the API Platform docs and in the bundle repository.

I took all the following steps from there:

  • Install the bundle
$ docker-compose exec php composer require lexik/jwt-authentication-bundle
  • Generate the RSA-keys
$ mkdir -p config/jwt

$ openssl genrsa -out config/jwt/private.pem -aes256 4096
Generating RSA private key, 4096 bit long modulus
..............................++
.............++
e is 65537 (0x10001)
Enter pass phrase for config/jwt/private.pem:
Verifying - Enter pass phrase for config/jwt/private.pem:

$ openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem
Enter pass phrase for config/jwt/private.pem:
writing RSA key

A new configuration file will be created.

The installation of the bundle also makes changes in the .env and .gitignore files.

You can adapt your key-folder and the passphrase in the .env file, while the keys won’t be added to your repository.

Add login and api section to the security configuration.
Make sure to add it before main section.

Add the access_control section to security and update the routes. I have added the prefix ‘api’ to all routes generated by api-platform.

Test the authentication with some requests

We have added two users before:

  • admin/admin_pw/ROLE_ADMIN
  • user1/user_pw

Obtain the token:

$ http --json POST localhost:8080/api/login_check username=admin password=admin_pw
HTTP/1.1 200 OK
Cache-Control: no-cache, private
Connection: keep-alive
Content-Type: application/json
Date: Sun, 03 Feb 2019 20:31:52 GMT
Link: <http://localhost:8080/api/docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"
Server: nginx/1.15.8
Transfer-Encoding: chunked
X-Debug-Token: 40879a
X-Debug-Token-Link: http://localhost:8080/_profiler/40879a
X-Powered-By: PHP/7.2.14

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NDkyMjU5MTIsImV4cCI6MTU0OTIyOTUxMiwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJ1c2VybmFtZSI6ImFkbWluIn0.YSs5Tuw9_lWhkXYL8P08NnA62kJ_ptAfVsL0nIPuRBM6pVxxauDaT-9Y8_IB4B_aqiyUBJA2mN6rf1SLVqTc_5NfQIDHgKBV_WoVMpyDHHIpvKZvaGE4fP6HwUL_2iQ2zvI4comQWJ8KI0YwS0Oqkzkg9Hlmj8ZRDwDxPFaRLVCN-vkVwM2x2Uueseng4ksu4KXnes_V9xg-BJAqlFBbgD5gHsGHZsfMTLTp-5oDFYPZvMeIlkkCD5Q_kSKEr1D2l-lWdgZl_DvNOLiwlikIj_CtQUedfqzjyXoCJygRnjejg-qpfxouyIhApAEPqeLsceho1t9Oy13Hh82FY4p5HnUT9oMHknhBe_jEPftjm2JR5bbSfRoMq1s9hcReNFGneS_untHUot2YKkAq9yrJsP97dOUTFQP8-bT1OtWIz3mP-NDV1uVL9BFL4GFBfZjzRV5Gjx3dF9r1mrS997z9xVExl3AD0zfA2-UzPcLYH4MviyeiT6A9xstsRqU1zzaGyOmAAoTCDaDiiFb2b0Lk19M0g5MbwENuzBzUJ3F3D98nR4QV2swXh_wxxy-VRHT9MGi62dYFlvZmkvoz8q0718WsmbONZ_NOfhgy8gLvmj9QsP0PaMA-Ex3-u1ZRniYLjKKqAb6MiS4owBd7xm2oMPULuJHiLawT_eAhA05gv5Y"
}

Make a request to the ressource:

$ http --json localhost:8080/api/users 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NDkyMjU5MTIsImV4cCI6MTU0OTIyOTUxMiwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJ1c2VybmFtZSI6ImFkbWluIn0.YSs5Tuw9_lWhkXYL8P08NnA62kJ_ptAfVsL0nIPuRBM6pVxxauDaT-9Y8_IB4B_aqiyUBJA2mN6rf1SLVqTc_5NfQIDHgKBV_WoVMpyDHHIpvKZvaGE4fP6HwUL_2iQ2zvI4comQWJ8KI0YwS0Oqkzkg9Hlmj8ZRDwDxPFaRLVCN-vkVwM2x2Uueseng4ksu4KXnes_V9xg-BJAqlFBbgD5gHsGHZsfMTLTp-5oDFYPZvMeIlkkCD5Q_kSKEr1D2l-lWdgZl_DvNOLiwlikIj_CtQUedfqzjyXoCJygRnjejg-qpfxouyIhApAEPqeLsceho1t9Oy13Hh82FY4p5HnUT9oMHknhBe_jEPftjm2JR5bbSfRoMq1s9hcReNFGneS_untHUot2YKkAq9yrJsP97dOUTFQP8-bT1OtWIz3mP-NDV1uVL9BFL4GFBfZjzRV5Gjx3dF9r1mrS997z9xVExl3AD0zfA2-UzPcLYH4MviyeiT6A9xstsRqU1zzaGyOmAAoTCDaDiiFb2b0Lk19M0g5MbwENuzBzUJ3F3D98nR4QV2swXh_wxxy-VRHT9MGi62dYFlvZmkvoz8q0718WsmbONZ_NOfhgy8gLvmj9QsP0PaMA-Ex3-u1ZRniYLjKKqAb6MiS4owBd7xm2oMPULuJHiLawT_eAhA05gv5Y'
HTTP/1.1 200 OK
....

Try the same with the user credentials. Because of defining in the users entity with an access_control attribute you should get 403-error.

HTTP/1.1 403 Forbidden

Testing with PHPUnit

To add PHPUnit to the project, we have to:

$ docker-compose exec php composer require --dev symfony/phpunit-bridge
$ docker-compose exec php ./bin/phpunit
$ docker-compose exec php composer require --dev symfony/browser-kit

To make tests easier, I added an in_memory user provider to the security config.

Now we can add some tests, the same we made on the CLI manually.

Run the tests with:

$ docker-compose exec php ./bin/phpunit
WARNING: The CONTAINER_REGISTRY_BASE variable is not set. Defaulting to a blank string.
#!/usr/bin/env php
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

Testing Project Test Suite
.2019-02-03T22:02:42+00:00 [error] Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException: "Access Denied." at /srv/api/vendor/symfony/security-http/Firewall/ExceptionListener.php line 118
.                                                                  2 / 2 (100%)

Time: 1.97 seconds, Memory: 26.00MB

OK (2 tests, 4 assertions)

And all green.


comments powered by Disqus