Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Hello API Security + API Docs on Production?

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Friends! Welcome to part two of our API Platform series - the one where we're talking all about ice cream, um, security. We're talking about security, not uh, ice cream. Hmm. Um, it's going to be almost as awesome as ice cream though, because the topic of security - especially API security - is fascinating! We've got API tokens, session-based authentication, CSRF attacks, dragon attacks and the challenge of securing our API Platform application down to the smallest details, like letting different users see different records or even returning or accepting different fields based on the user. Yep, we're going to take this wonderful base that API platform has given us and shape it to act exactly like we need from a security perspective.

The great thing about API platform is that it's basically just... Symfony security. They didn't reinvent the wheel at all. If you haven't gone through our Symfony Security tutorial yet, now would be a good time: you'll feel much more dangerous after.

Anyways, let's dive into this! To put the lock down on your API Platform security skills, download the course code from this page and code along with me. After you unzip the file, you'll find a start/ directory that has the same code you see here. Open up the README.md file for all the details on getting the app set up. If you coded through part one of this tutorial, um, you rock, but also, I recommend downloading the new course code because I upgraded a few dependencies and added a frontend to the site.

Anyways, one of the last steps in the README will be to open a terminal, move into the project and run:

yarn encore dev --watch

to run Encore and build some JavaScript that'll power a small frontend. To make things more realistic, we'll do a little bit of work in JavaScript to see how it interacts with our API from an authentication standpoint. Next, open another terminal and start the Symfony local web server:

symfony serve

Once that starts, you should be able to fly back over to your browser and open up https://localhost:8000 to see... Cheese Whiz! The peer-to-peer cheese-selling app we build in part 1. Actually, this is our brand new Vue.js-powered frontend. OOOOooOOO. Go to /api. Ah yes, this is the API we built: we have a CheeseListing resource, a User resource, they're related to each other and we've customized a ton of stuff. In a bit, we'll start making this frontend talk to the API.

Hiding the Docs on Production?

But before we get there, head back to /api to see our beautiful, interactive, Swagger documentation. I know some of you are probably thinking: I know, this is great for development, but how can I disable this for production?

The answer is... you shouldn't!

Well first, let me show you how you can disable this on production and then I'll try to convince you to keep it.

Open up config/packages/api_platform.yaml. API Platform is highly configurable. So, reminder time: if you go to an open terminal, you can see all your current configuration - including any default values - by running:

php bin/console debug:config api_platform

You can also run:

php bin/console config:dump api_platform

to see an example tree of all the possible keys with some explanations. One of the options is called enable_docs. Copy this. The goal is to disable the docs only in production, but let's start by disabling them everywhere to see how it works.

Set enable_docs to false.

... lines 2 - 17
enable_docs: false

Ok, go back and refresh /api. Um... there is absolutely no change! That's due to a small dev-only bug with a few of these options: when we change those options, they're not causing the routing cache to rebuild automatically. No big problem, clear that cache manually with:

php bin/console cache:clear

Fly back over, refresh and... yep! The documentation is gone. Oh... but instead of a 404, it's a 500 error!

It turns out that going to /api to see the docs was just a convenience. The documentation was really stored at /api/docs and this is now a 404. You could also go to /api/docs.json or /api/docs.jsonld before, but now it's all gone.

One of the unfortunate things about removing the documentation is that it wasn't just rendered as HTML. In part 1 of this series, we talked about JSON-LD and Hydra, and how API Platform generates machine-readable documentation to explain your API. If I go to /api/cheeses.jsonld, this advertises that we can go to /api/contexts/cheeses to get more "context", more machine-readable "meaning". Whelp, that doesn't work anymore. The point is: if you disable documentation, realize that you're also disabling the machine-friendly documentation.

And if you want to fix the 500 on /api, you also need to disable the "entrypoint". Right now, if we go to /api/index.jsonld, we get a, sort of, "homepage" for our API, which tells us what URLs we could go to next to discover more. When we go to /api, that's the HTML entrypoint and that's... totally broken. To disable that page set enable_entrypoint to false. Rebuild the cache:

... lines 2 - 18
enable_entrypoint: false
php bin/console cache:clear

then go refresh. Now we get a 404 on this and /api.

So to fully disable your docs without a 500 error, you need both of these keys. To make this only happen in production, copy them, delete them, and, in the config/packages/prod directory, create a new api_platform.yaml file. Inside, start with api_platform: then paste.

enable_docs: false
enable_entrypoint: false

If we changed to the prod environment and rebuilt the cache, we'd be done!

But instead... I'm going to comment this out... for two reasons. First, I like the documentation, I like the machine-readable documentation and I like the "entrypoint": the documentation homepage. And second, more importantly, if you want to hide your documentation so that nobody will use your API, that's a bad plan. That's security through obscurity. If your API lives out on the web, you need to assume people will find it and you need to properly secure it. Hey! That's the topic of this tutorial!

Rebuild the cache one more time.

At the end of the day, if you're ok keeping the machine-readable docs and the entrypoint stuff, but you really don't want to expose the pretty HTML docs, you could always create an event subscriber on the kernel.request event - now called the RequestEvent in Symfony 4.3 - and, if the URL is /api and the request format is HTML, return a 404.


You can see an example here: https://bit.ly/2YmR6Uh

Ok, let's get into the real action: let's start figuring out how we're going to let users of our API log in.

Leave a comment!

Login or Register to join the conversation
hanen Avatar

Hi everyone... When I execute yarn encore dev --watch command ..I get this error
yarn run v1.15.2
error Couldn't find a package.json file in "C:\\users\\hanen\\sites\\api_platform"
I run these commands for installing Encore on Symfony : composer require symfony/webpack-encore-bundle
yarn install
it works

2 Reply
William C. Avatar
William C. Avatar William C. | hanen | posted 2 years ago

Thanks, exactly what I needed.

1 Reply

Hey hanen

Can you double check that a package.json file exists at the root of your project?
Also, could you try running yarn watch instead?


hanen Avatar

hi Diego
webpack is watching my files, In my public directory create 3 files: runtime.js , app.css& app.js


Oh, great, so Webpack is working now as expected, right?

kkedzierski Avatar
kkedzierski Avatar kkedzierski | posted 2 months ago

I had always problems to start chapter 2 of api platform tutorial because of issues during instalation of this project.
Now i using a 8.1 version of php on Ubuntu and after read all comments i think the simplest solution is:

1) Choose the php 8.0 version by this command:

sudo update-alternatives --config php

or install 8.0 if you don't have it (is many tutorials on web how to install 8.0) and then choose that version by:

sudo update-alternatives --config php

(Below is comment by Weaverryan author of this tutorial)
2) In composer.json, find all "4.3." and replace with "4.4.". Symfony 4.4 has proper PHP 8 support, but 4.3 does not.

3) Run composer up --ignore-platform-reqs

And thats all, you can go now through the all commands from readme:

  • php bin/console doctrine:database:create
  • php bin/console doctrine:migrations:migrate
  • yarn install
  • yarn encore dev --watch
  • symfony serve -d

For me that's work.

1 Reply

Thanks for posting your solution kkedzierski! We're going to update this tutorial for API Platform 3 soon, but in the mean time, this is very helpful!

Sakshi G. Avatar
Sakshi G. Avatar Sakshi G. | posted 7 months ago

How to make code you created in previous course part-1 work with this code:(even with php 8)
-->from start directory add following:-
add asset folder
add bootstrap.php
add controller
add frontend template folder
add package.json (if not present)
add webpackencore.yaml(inside confing/packages)

run composer install
run composer update
yarn install
yarn encore dev-server

It worked for me in php 8 !

1 Reply

Thanks for posting Sakshi G. - awesome!

1 Reply
Riya J. Avatar
Riya J. Avatar Riya J. | Sakshi G. | posted 7 months ago | edited

Yeah, It worked for me too! Thank you Sakshi G. sharing this! :)

1 Reply

I am using PHP 8.1 and can't run `composer install`... any ideas? I am using Windows 10 and not interested in using any *AMP programs.

1 Reply

Hey Budmanz,

Yeah, that's a known issue in this tutorial, the course code support only PHP version ^7.1.3 that does not allow to PHP 8, we show this as in a JS tooltip when you try to download the course code. Possible solutions would be to tweak that PHP version in composer.json, but then you may need to upgrade dependencies to fetch new ones that will support PHP 8. But it will mean that your code may be slightly different from one we're showing in this tutorial. I'd recommend you to use a legacy PHP 7.4 to follow this course if you can.


2 Reply

Thanks, Victor! I will try your suggestion. I have used SF since SF2 but never encountered a project like the one I am doing now where I need to consume api's with authentication (JWT). So, I think this is the wrong tutorial for my purpose; do you have one that you can recommend for me?



Hey Bud,

Oh, nice, since Sf 2! I started using Symfony since that version as well :)

If you're looking for JWT course, you probably need to look at this one: https://symfonycasts.com/sc... - but this is the 4th episode, you may want to watch 3 previous I suppose, there's the link to the course group: https://symfonycasts.com/tr... - well, those tutorials are based on the older version of Symfony but JWT concepts we're teaching in that 4th course are still valid for today.

Well, JWT is redundant in case when only YOUR app is sending API requests to the server, because in this case you can simplify the system dramatically asking users to log in into their account first and then all the API requests will be authenticated, we're talking about the difference in these videos IIRC: https://symfonycasts.com/sc... and https://symfonycasts.com/sc... and show the simpler way instead of JWT. But if you really need JWT in your project - then look at those course I mentioned above probably, we haven't talked about JWT in other courses unfortunately.

Oh, or take a look at this video too: https://symfonycasts.com/sc... - might be useful for you I suppose.

I hope that helps!


2 Reply
Mehul-J Avatar
Mehul-J Avatar Mehul-J | posted 2 months ago | edited

After set up of the project using php 7.4 and open up browser i am getting
(1/1) ParseError
syntax error, unexpected token "match"
in Negotiator.php line 41
at DebugClassLoader->loadClass()

Jakub Avatar
Jakub Avatar Jakub | Mehul-J | posted 10 days ago | edited

Hi Mehul-J,

I've had the same problem. Probably you've managed your problem till now, but if someone will have same issue, maybe my solution will help. In my case increasing version of symfony to 4.4, api platform core to version 2.6 and adding "willdurand/negotiation": "3.0.0" in require section in composer.json helped. You need to be aware that also on require-dev section the Symfony packages versions also need to be increased. After this, you only need to run composer update instead of composer install.

Kind regards,

1 Reply

Hey @Mehul-J!

Hmm. The match function was introduced in php 8. So, somehow it seems like you got a version of the project that's only compatible with php 8, but while using PHP 7.4. Did you download the course code from this page (that should actually only work with php 7.*)? Or did you setup the project a different way?



I followed the instructions of the read me and got this error?

[Semantical Error] The annotation "@ApiResource" in class App\Entity\CheeseListing was never imported. Did you maybe forget to add a "use" statement for this annotation?

I deleted the ApiResource() annotation from the cheeselisting entity so that I could isolate the issue and also view the front end but I got another error:

[Semantical Error] The annotation "@ORM\Entity" in class App\Entity\CheeseListing was never imported. Did you maybe forget to add a "use" statement for this annotation?

Any idea on how to proceed? Kind regards


Hey mercelot

That's unexpected :)
Makes me think you didn't run composer install or perhaps something odd happening when installing your vendors. Could you try the following
- Remove completely the vendor directory
- Run composer install
- Try again, if the problem persists. Delete Symfony's cache manually rm -r var/cache/* and try again

If nothing worked, let me know. Cheers!

Guerber C. Avatar

Hello, I have tried the following procedure without success.

composer.json : "php": "^7.1.3|^8.0"
composer install --ignore-platform-reqs

this gives me the following error :

[Semantical Error] The annotation "@ApiResource" in class App\Entity\User was never imported. Did you maybe forget to add a "use" statement for this annotation?



Double check the use statemens on your User class, you may be missing one. The tutorial starts with these ones:

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Serializer\Filter\PropertyFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
Guerber C. Avatar


Thank you for your reply, I have checked all the imports, no one is missing but the error persists.



Hey Guerber C.!

Sorry about the troubles - that's not the experience we want! I just tried it locally. The problem is that the code, unfortunately, only works with PHP 7: some of the dependencies are too old to support PHP 8, and so it gives you this odd error. However, you CAN get it working with PHP 8 without too much trouble. Here's what I did:

A) In composer.json, find all "4.3.*" and replace with "4.4.*". Symfony 4.4 has proper PHP 8 support, but 4.3 does not.
B) Run composer up --ignore-platform-reqs.

That should get things working. Or, at least for me, it was able to install all the dependencies without any trouble :). It's possible you still may hit some PHP 8 issues if some dependencies are still too old, and there may be minor differences between the tutorial and what you see in your code... but those should be minor.

I hope this helps! The tutorial is still really solid - but as newer PHP versions are released, sometimes these tutorials have trouble (and we intentionally don't update the download code because we want it to match the video exactly).


Christopher S. Avatar
Christopher S. Avatar Christopher S. | posted 1 year ago

Hi. Tried to make the code work with PHP 8.0 and Symfony 4.4. But after updating the composer.json and trying to run composer update I run into

Script cache:clear returned with error code 255
!! PHP Fatal error:
Uncaught Symfony\Component\Debug\Exception\ClassNotFoundException:
Attempted to load class "DoctrineCacheBundle" from namespace


Hey Daniel,

DoctrineCacheBundle is abandoned as you can see here: https://github.com/doctrine... - you should avoid using it. If it was just a dependency that you do not use - just remove it. If you use it - probably consider replacing it in favor of Symfony Cache component. Most probably that bundle isn't a required dependency now, so you can just remove that line from the bundles.php list and it should work. But if you need that bundle, make sure you have "doctrine/doctrine-cache-bundle" installed in your project, and make sure that "Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle" class exist there.

I hope this helps!


Mike L. Avatar
Mike L. Avatar Mike L. | posted 1 year ago

I've got an error unable to fetch the response from the backend: unexpected EOF - not sure what the offending file is


Hey Mike L. !

Hmmm. Yea... I've seen that error, but it's rare. I assume you're using the symfony web server? If so, try running symfony server:log and then try again. Assuming you get this same error, go back and check your terminal. Hopefully you will see an error in the logs on the screen that will help :).


Jens W. Avatar
Jens W. Avatar Jens W. | posted 2 years ago

To save others time. The package is realy outdated now. In order to make this run in windows 10 you need to install Phython 2 and set the systemvariable, not the last Version 3 - they are not compatible. then you need to downgrade yarn to version 1.22: "yarn set version 1.22" and you need .net Famework 2. then you could use the commands from the read me. if you still get "encore" errors do: "composer require symfony/webpack-encore-bundle" like hanene suggests.

but maybe its time to update the course. ;)


Hey Jens W.!

Ah, you're absolutely right! The problem is a not-too-important (but still-totally-breaking-the-build) dependency of node-sass. This dependency is a pain, as you constantly need new versions of it for new operating systems so that it can build. And... it actually *builds* an executable, which requires other dependencies (which is why you hit the python thing). Fortunately, the JS world has fixed this and you can now use sass (a pure JS binary for sass) instead of node-sass.

I've just updated the course download code to include sass instead - you should be able to easily build without any issues now :). But if you have any other issues, please let us know. And thanks for reporting this!


Jens W. Avatar

Hey Ryan, works great now! :)

btw. i saw you on symfony world. i thought: man you know this voice. gosh .. thats the guy from symfocast. made my day!


Hey Jens W.!

Sweet! Happy to hear it - i've updated a bunch other courses to fix this problem too :).

> btw. i saw you on symfony world. i thought: man you know this voice. gosh .. thats the guy from symfocast. made my day!

Ha! I hope you enjoyed it - it was fun to at least - "kind of" - get together with people ;).


Roland W. Avatar
Roland W. Avatar Roland W. | posted 2 years ago

I installed Yarn as described here https://yarnpkg.com/getting... – it installed version 2.0.0-rc.31 for this project. After that yarn install fails:

➜ start git:(master) ✗ yarn install
➤ YN0000: ┌ Resolution step
➤ YN0032: │ nan@npm:2.14.0: Implicit dependencies on node-gyp are discouraged
➤ YN0032: │ evp_bytestokey@npm:1.0.3: Implicit dependencies on node-gyp are discouraged
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide jquery@1.9.1 - 3 requested by bootstrap@npm:4.3.1
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide popper.js@^1.14.7 requested by bootstrap@npm:4.3.1
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide webpack@^3.0.0 || ^4.0.0 requested by sass-loader@npm:7.1.0
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide css-loader@* requested by vue-loader@npm:15.7.1
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide webpack@^4.1.0 || ^5.0.0-0 requested by vue-loader@npm:15.7.1
➤ YN0000: └ Completed in 10.41s
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 22.52s
➤ YN0000: ┌ Link step
➤ YN0007: │ core-js@npm:3.1.4 must be built because it never did before or the last one failed
➤ YN0007: │ node-sass@npm:4.12.0 must be built because it never did before or the last one failed
➤ YN0007: │ fsevents@patch:fsevents@npm%3A1.2.9#builtin<compat fsevents="">::version=1.2.9&hash=77dfe6 must be built because it never did before or the last one failed
➤ YN0007: │ core-js-pure@npm:3.1.4 must be built because it never did before or the last one failed
➤ YN0000: └ Completed in 3.54m
➤ YN0000: Done with warnings in 4.09m

Using the globally installey version 1.22.4 of Yarn gives me warnings as well – but seems to work:

➜ start git:(a1a3cfd) yarn install
yarn install v1.22.4
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
warning mini-css-extract-plugin@0.4.2: Invalid bin field for "mini-css-extract-plugin".
[3/4] 🔗 Linking dependencies...
warning " > bootstrap@4.3.1" has unmet peer dependency "jquery@1.9.1 - 3".
warning " > bootstrap@4.3.1" has unmet peer dependency "popper.js@^1.14.7".
warning " > sass-loader@7.1.0" has unmet peer dependency "webpack@^3.0.0 || ^4.0.0".
warning " > vue-loader@15.7.1" has unmet peer dependency "css-loader@*".
warning " > vue-loader@15.7.1" has unmet peer dependency "webpack@^4.1.0 || ^5.0.0-0".
[4/4] 🔨 Building fresh packages...
✨ Done in 477.86s.

My setup:
MacOS 10.15.4
Node 13.12.0
Npm 6.14.4


hey Roland W.

Sorry getting you confused with this yarn versions. Unfortunately not everything is compatible with Yarn v2. This version of yarn is completely new and there is no backward compatibility with v1. I'll check if we can add a note about it!

Thank you for your feedback.


4:10 I always pipe this output through `bat -l yaml` to get syntax highlighting. https://github.com/sharkdp/bat


Haha, yea - that's awesome!

1 Reply

Hey Tomasz,

Thanks for this tip, it might me useful when working in console :)


1 Reply
Igor P. Avatar
Igor P. Avatar Igor P. | posted 3 years ago

Hi. Is there any ability to get those docs as static html?


Hey tmp0000,

Hm, not sure if it's possible to do out of the box... You can do right click somewhere on the docs and save the current page as HTML, but you would need to do this for every HTML page :) Well, you can output the docs to JSON or Yaml format using the specific console command for it:

$ bin/console api:openapi:export

Probably, you can write a little script that will iterate the output data from that command and run curl request to get those pages automatically, but you would also need to think about assets in this case.

I hope this helps!


Boufares Avatar
Boufares Avatar Boufares | posted 3 years ago

I would like to combine Session and JwtBundle
I explain I want to allow a connection via the Session and a second connection for my Api via JwtBundle.
I tested the explanation that is on the course JWT REST video 1 (2 Firewalls) but when I connected A I made an ajax call that returned a 401.
Thanks for your help


Hey @zakaria!

The problem with 2 firewalls is that only 1 firewall can be active for each request. 2 firewalls works great if you want (for example) 1 firewall to be active for all URLs starting with /api (and maybe you use JWT for this firewall) and another firewall for all the other URLs (and maybe you use a form login for this).

In your case, you want the *same* URLs (e.g. /api/cheeses) to allow session-based authentication or JWT. The way to do that is with one firewall, and then just two "keys" under that firewall (e.g. json_login for the session-based stuff and also whatever "jwt" authentication mechanism you need). The only "catch" is that, on a technical level, if someone uses JWT, your Symfony app will start a session for that and try to return a cookie (any API client will just ignore the cookie, but a session was technically started). That's just due to the fact that a firewall is either completely stateless (no sessions) or completely stateful (always sessions) - so your firewall will always use a session, even if the user is authenticating with JWT.

Let me know if this helps!


1 Reply
Eric H. Avatar
Eric H. Avatar Eric H. | posted 3 years ago

I don't know if it is just me, but when I try using the api platform interface to create a user, it doesn't bcrypt the password, any idea why that is?


Hey Eric H.!

That's correct! We haven't handled that yet - we'll do it around chapter 12 :).


Htun htun H. Avatar
Htun htun H. Avatar Htun htun H. | posted 3 years ago

Hi when this tutorial will be available ?


Hi Htun htun H.!

We'll start releasing this tutorial next week :)


Htun htun H. Avatar

Awesome !!!! can't wait to try this out :)

Cat in space

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

This tutorial works great for Symfony 5 and API Platform 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.1.3, <8.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.5
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.13.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.6
        "nesbot/carbon": "^2.17", // 2.21.3
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.3.*", // v4.3.2
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/expression-language": "4.3.*", // v4.3.2
        "symfony/flex": "^1.1", // v1.18.7
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/http-client": "4.3.*", // v4.3.3
        "symfony/monolog-bundle": "^3.4", // v3.4.0
        "symfony/security-bundle": "4.3.*", // v4.3.2
        "symfony/twig-bundle": "4.3.*", // v4.3.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.6", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    "require-dev": {
        "hautelook/alice-bundle": "^2.5", // 2.7.3
        "symfony/browser-kit": "4.3.*", // v4.3.3
        "symfony/css-selector": "4.3.*", // v4.3.3
        "symfony/maker-bundle": "^1.11", // v1.12.0
        "symfony/phpunit-bridge": "^4.3", // v4.3.3
        "symfony/stopwatch": "4.3.*", // v4.3.2
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.2