Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

Testing Token Authentication

This Chapter isn't
quite ready...

Rest assured, the gnomes are hard at work
completing this video!

Browse Tutorials

What about a test like this... but where we log in with an API key? Let's do that! Create a new method: public function testPostToCreateTreasureWithApiKey().

This will start pretty much the same as before. I'll copy the top of the previous test, remove the actingAs()... and add a dump() near the bottom. So, like before, we're sending invalid data and expect a 422 status code.

Copy that method name, then spin over and run just this test:

symfony php bin/phpunit --filter=testPostToCreateTreasureWithApiKey

And... no surprise: we get a 401 status code because we're not authenticated.

Let's send an Authorization header, but an invalid one to start. Pass a headers key set to an array with Authorization and then word Bearer and then... foo.

This should still fail:

symfony php bin/phpunit --filter=testPostToCreateTreasureWithApiKey

And... it does! But with a different error message: invalid_token. Nice!

Using a Real Token

To pass a real token, we need to put a real token into the database. Do that with $token = ApiTokenFactory::createOne().

Do we need to control any fields on this? We actually do. Open up DragonTreasure. If we scroll up, the Post operation requires ROLE_TREASURE_CREATE. When we authenticate via the login form, thanks to role_hierarchy, we always have that. But when using an API key, to get that role, the token needs the corresponding scope.

To make sure we have it, back in the test, set the scopes property to ApiToken::SCOPE_TREASURE_CREATE.

Now pass this to the header: $token->getToken(). Oh... and let me fix scopes: that should be an array.

I think we're ready! Run that test:

symfony php bin/phpunit --filter=testPostToCreateTreasureWithApiKey

And... got it! We see the beautiful 422 validation errors!

Testing a Token with a Bad Scope

Let's test to make sure we don't have access if our token is missing this scope. Copy the entire test method... then paste below. Call it testPostToCreateTreasureDeniedWithoutScope().

This time, set scopes to something else, like SCOPE_TREASURE_EDIT. Below, we now expect a 403 status code.

This time, let's run all the tests:

symfony php bin/phpunit

And... all green! A 422 then a 403. Go remove the dumps from both those spots.

By the way, if you use API tokens a lot in your tests, passing the Authorization header can get annoying. Browser has a way where we can create a custom Browser object with custom methods. For example, you could add an authWithToken() method, pass an array of scopes, and then it would create that token and set it into the header. This totally does not work right now, but check out Browser's docs to learn how.

Next: in ApiPlatform 3.1, the behavior of the PUT operation is changing. Let's talk about how, and what we need to do in our code to prepare for it.

Leave a comment!

Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.0", // v3.1.2
        "doctrine/annotations": "^2.0", // 2.0.1
        "doctrine/doctrine-bundle": "^2.8", // 2.8.3
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.1
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.66.0
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.16.1
        "symfony/asset": "6.2.*", // v6.2.5
        "symfony/console": "6.2.*", // v6.2.5
        "symfony/dotenv": "6.2.*", // v6.2.5
        "symfony/expression-language": "6.2.*", // v6.2.5
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.5
        "symfony/property-access": "6.2.*", // v6.2.5
        "symfony/property-info": "6.2.*", // v6.2.5
        "symfony/runtime": "6.2.*", // v6.2.5
        "symfony/security-bundle": "6.2.*", // v6.2.6
        "symfony/serializer": "6.2.*", // v6.2.5
        "symfony/twig-bundle": "6.2.*", // v6.2.5
        "symfony/ux-react": "^2.6", // v2.7.1
        "symfony/ux-vue": "^2.7", // v2.7.1
        "symfony/validator": "6.2.*", // v6.2.5
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.1
        "symfony/yaml": "6.2.*" // v6.2.5
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "mtdowling/jmespath.php": "^2.6", // 2.6.1
        "phpunit/phpunit": "^9.5", // 9.6.3
        "symfony/browser-kit": "6.2.*", // v6.2.5
        "symfony/css-selector": "6.2.*", // v6.2.5
        "symfony/debug-bundle": "6.2.*", // v6.2.5
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/phpunit-bridge": "^6.2", // v6.2.5
        "symfony/stopwatch": "6.2.*", // v6.2.5
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.5
        "zenstruck/browser": "^1.2", // v1.2.0
        "zenstruck/foundry": "^1.26" // v1.28.0