API Platform Part 2: Security


What you'll be learning

Yep! You ❤️your new API Platform-powered API! It's just missing... well... any type of security! This is a big & important topic, so let's take it head-on in part 2 of our API Platform tutorial:

  • API token security? Or tried-and-true session based login form security?
  • CSRF protection? SameSite Cookies? Ice Cream?
  • Security firewall setup for json_login authentication
  • Authorization & roles: restricting access to your operations!
  • Encoding user's password (during user creation/update)
  • API Platform custom data persister
  • Dynamic serialization groups: showing different fields based on the user
  • Custom normalizer for dynamic fields based on user
  • Custom validator to control what data a user can set

Woh. Let's do this!

Your Guides

Niels van der Molen Ryan Weaver

Buy Access
Login or register to track your progress!

Questions? Conversation?

  • 2020-06-10 Victor Bocharsky

    Hey Steve,

    Oh, I see. Well, first of all you can check config/packages/doctrine.yaml file where we set server_version to 5.7. Thanks to this all the migrations are generated for MySQL 5.7+, that's why it may not work for your 5.6 as I see. In this case, you need to regenerate migrations with that server_version set to 5.6. But for dev/learning purposes, you can ignore migrations and just run the next command, but set server_version to 5.6 first so that Doctrine perfor specific queries:

    $ bin/console doctrine:schema:update --force

    But it's not recommended to run this on production when you have some data, it may truncate them. If you wonder what queries will be executed - you can check it with this command first:

    $ bin/console doctrine:schema:update --dump-sql

    I hope this helps!


  • 2020-06-09 Steve Linberg

    This tutorial appears to require MySQL 5.7, using a JSON data type for user.roles in src/Migrations/Version20190509185722.php. I am stuck on 5.6 for compatibility reasons.

    If this is changed to TEXT, will the code work?

  • 2020-04-17 Vladimir Sadicov

    Yeah sorry about that, but you should have a subscription or buy this course to have access.


  • 2020-04-17 Maboa Garaboa

    Thanks for the information! Looks like I do not have access to download the code

  • 2020-04-17 Vladimir Sadicov

    Hey Maboa Garaboa

    It's always located in the root of your project and if you are talking about course code, than you can find it in the start/ or finish/ folder depending on what are you looking.


  • 2020-04-16 Maboa Garaboa

    Awesome tutorial!

    Where can I find the package.json file?

  • 2019-09-11 Simon Denman

    Thanks Diego,
    Yes, I think the simplest option is just to add the annotated fields manually.
    Just thought I'd check if I was missing something :)

  • 2019-09-10 Diego Aguiar

    Hey Simon Denman

    I think there is a way but it might be ugly. What you can do is to create your own Serializer and add those fields into your response. Give it a check to the docs: https://api-platform.com/do...

    But, I think it would be easier if you just add those properties manually to your entity instead of using a trait


  • 2019-09-10 Simon Denman

    Don't suppose there's a shortcut way to expose Blameable / Timestampable fields to the API - i.e. expose the trait itself??
    I'm guessing not and that you have to add the fields to your entities manually.

  • 2019-08-26 Ramazan

    Hey weaverryan ,

    Yep it's exactly what I meant with the search text filter. It will be great to touch this topic at least with something like, how to search faster with elasticsearch... on the same project. Maybe on the 3rd tutorial :)

    Good luck.

  • 2019-08-19 weaverryan

    Hey Ramazan!

    Ah, yep, that makes sense. Some of the filters - like the boolean filter - are fast... as it ultimately create something like WHERE is_published=1... which is fast (and even faster if you decide to add an index on that field). But there is indeed a performance problem in large datasets with the "search" filter... because this creates the WHERE name LIKE %foo% type of query. The search filter sometimes isn't powerful enough - it's still a pretty "simple matching" search. If you need a more robust search or a more performant search, the proper solution is to use ElasticSearch and the ElasticSearch integration with API Platform. That's something we're thinking about covering sometime in the future (we're getting quite a lot of requests), but won't be part of this tutorial :).

    I hope that helps!


  • 2019-08-19 weaverryan

    Hey Jarrod!

    Happy we were on the same page :).

    > The only question I really had was if I should use Filters or Extensions

    I think the difference is that filters will ultimately be an optional thing that someone using your API may or may not use - e.g. ?isPublished=1 is something a user *might* add to only show published results. But if you want to *force* this "filtering" on them (not let them choose), extensions can do that. So, security tends to fit better into an extension, but it always depends on your situation.


  • 2019-08-15 Jarrod

    Hey weaverryan

    Thank you so much for your reply! It was very helpful and exactly falls in line with what I was thinking (using extensions for collection access control and voters for item). The only question I really had was if I should use Filters or Extensions but sounds like Extensions are the way to go. Maybe there could be a section on the differences between the 2 that helps explain which should be used when?

    Having the logic near each other in a service that both the voters and extensions call upon is a great idea that I hadn't thought of. This should make the access control logic more centralised and developer friendly so is nice solution to an ugly problem.

    Thanks again,

  • 2019-08-15 Ramazan

    Thank you very much weaverryan for your quick response for the login tip, it works :)

    What I mean by performance here is about the response time, if you have a lot of data in your database, the filters that we have in the get cheeses could impact the response time with MySql. So I wonder what's the best to do to handle a cheeses search if I have a lot in my DB.
    Maybe I should create a custom controller and handle the situation from there and do the search on a different data source...

  • 2019-08-15 weaverryan

    Hey Ramazan!

    > Thank you very much for the great work you do with these API Platform tutorials


    > Is there a way for me (as a backend developer) to login via Swagger so I can test the whole authentication part?

    You somehow just need to "login" so that the session cookie gets set. You could probably do this via your browser's JavaScript console. Try running this in your browser's debug console:

    fetch('https://localhost:8000/login', {
    method: 'POST',
    body: JSON.stringify({
    email: 'cheeseplease@example.com',
    password: 'foo'
    headers: {
    'Content-type': 'application/json; charset=UTF-8'

    In theory... though I've never tried it, that will successfully authenticate and set the authenticated session cookie in your browser. Let me know if that works!

    > And I wish maybe in a next serie, you will continue with something like "how to get better performance with your API Platform" with filters, text search

    I'd love to hear more about this. Filters & text search are a nice feature to have to make your API more usable. How do you see that this relates to performance?


  • 2019-08-15 Ramazan

    Hi SymfonyCasts team,

    Thank you very much for the great work you do with these API Platform tutorials.

    As I'm not a frontend developer, I even don't want to install the frontend part of this tutorial.
    Is there a way for me (as a backend developer) to login via Swagger so I can test the whole authentication part?

    Actually I can login with Postman but I think it's better to do it with Swagger UI as my endpoints are already in place.

    And I wish maybe in a next serie, you will continue with something like "how to get better performance with your API Platform" with filters, text search...

  • 2019-08-15 weaverryan

    Hey Jarrod!

    > Loving the API Platform specific SymfonyCasts

    Thank you! ❤️

    > Just wondering if you'll be covering more advanced access control concepts

    Definitely, but questions are always welcome early in a tutorial in case we're missing some legit situation!

    > For item specific endpoints I'm using voters to test for for various conditions

    Excellent choice - we'll do the same thing in this tutorial.

    > however this doesn't work for collection endpoints as looping over all results and applying voters to strict data would mess with the pager as well as add large processing overheads

    Yep, this is a classic security problem and not (generally speaking) specific to API Platform. What you *want* is to write *one* piece of security logic and have it work for both fetching a single item and for "filtering" your collection. That's just not really possible. The problem is that these two situations end up being implemented very differently. For an item, you START with the entity object, then can use that in php if statements to figure out if the user should have access to that one item. For a collection, you basically need to take you security logic and "express it" via a query.

    The solution in API Platform for collections is an extension https://api-platform.com/do.... So, you'll have voters for applying security to a single item and an extension for modifying the query on a collection. This will mean that at least some of your security logic is duplicated - I've never seen a way to avoid this. The trick is to keep any complex security logic "close" to each other so that if you change one thing, you'll know to change the other. You might, for example, create a shared service that both the voter and extension call which has a method for deciding access on a single item and another method for modifying a QueryBuilder. Then, at least the logic would be near each other.

    Let me know if this helps!


  • 2019-08-13 Jarrod

    Loving the API Platform specific SymfonyCasts. Nice clear and easy to understand. Wish these were out a year ago when I started building my API Platform based API haha. Would have saved me a lot of time researching how to do X.

    Just wondering if you'll be covering more advanced access control concepts E.G. How to restrict a collection of related entities either by using Filters, Extensions, Voters or some other means? All examples I've seen are simple and require a user relationship on the entities which are checked against the current logged in user etc. When access control rules are more complex this gets difficult.

    For item specific endpoints I'm using voters to test for for various conditions however this doesn't work for collection endpoints as looping over all results and applying voters to strict data would mess with the pager as well as add large processing overheads.

    More complex example.
    - User 1 has a relationship with Product 1 = Can view Product 1
    - User 1 has a relationship with Group 1 = Should only be able to see their own Products so can view Product 1
    - User 2 has a relationship with Group 1 = Should only be able to see their own Products so can't view Product 1
    - User 3 is an admin of Group 1 = Should be able to view all products for all Users in Group 1 so can view Product 1
    - User 4 is a "super" admin = Should be able to view all products from all users

    Maybe it'd be better suited for a more advanced course.. Or is there already another SymfonyCast I have somehow missed that addresses this?

  • 2019-08-12 Victor Bocharsky

    Hey Alberto,

    For now it's only 9, but it's difficult to say the total, we can add a few more at the last moment. Stay tuned for more updates.


  • 2019-08-10 Alberto

    how many videos will this security part have?

  • 2019-08-06 alsbury

    Thanks for the feedback, I really appreciate you taking the time to respond to my questions. Cookies sound like the best option. JWT tokens would probably only improve my situation if the service needed to scale really big.

  • 2019-08-06 weaverryan

    Hey alsbury!

    > Is there a way that the firewall statefulness can be dynamic so that you can choose based on request context

    I'm pretty sure this is not possible (just checked some code). But, I'm not sure that's a problem. If you *did* want to use something like JWT for your mobile app, you can still make the request to the same firewall. Yes, this will start a session and send back a cookie. But your mobile app can ignore the cookie and use the JWT instead if you want. You could also (not sure if this would be considered a good practice, but it would certainly work) add some "flag" (e.g. POST param) to your authentication request that indicates whether or not the success response should include a JWT or not (if you want to avoid getting a JWT sent back to your JS... though again... you could just ignore that).

    I don't know if I see a big benefit in doing this - though, there could be some good reason - there are so many variations with this stuff. I would use cookie-based authentication on the mobile app OR if you want to go the full OAuth route (which has the downside of complexity) I think the "most" recommended solution for mobile apps is the "authorization code" OAuth grant type with no client secret but with PKCE.


  • 2019-08-05 alsbury

    I'd considered using cookies on the mobile app side. JWT (access/refresh) tokens seemed like a nice choice for the mobile app, but sessions could certainly be used instead. That may just be the most practical solution in my scenario. My app is "owned" by me, so I can avoid OAuth and some of the more complex scenarios. Is there a way that the firewall statefulness can be dynamic so that you can choose based on request context? My goal is to avoid duplicating routes and still use the auth mechanism best suited for each particular platform. Maybe I am making a mountain out of a mole hill...I don't know. Thanks for all your feedback.

  • 2019-08-05 weaverryan

    Hey alsbury!

    Thanks for the added notes here - great stuff! This is not he only solution but to play "devil's advocate", you could have a stateful firewall that uses a session cookie that is then used by your own JavaScript front-end and a mobile app (mobile apps support cookies). If you used an OpenId authentication server, web app & mobile app could use that with the auth code grant type... the downside being that this requires more setup & complexity if you don't actually need it. If the mobile app is "owned" by you, then OAuth isn't really needed. Let me know how this "jives" with what you're thinking. There are still so many options... and, unfortunately, many possible different attack vectors with the different solutions.

    P.S. You're right that having a refresh token in JavaScript is a bad idea, unfortunately :/.


  • 2019-08-05 alsbury

    Thanks for the response Ryan. In the research I'd done up to this point, it felt reasonably safe to store the access token (short lived token) as a cookie. But if your token lives for 15 minutes, then you need a new token in 15 minutes. This would require you to login again. Or if you simply reloaded the page, you would have to login again as well. So you add a refresh token to the mix. But a refresh token has a lot of power. If you store it as a cookie too, why bother with an access token. Both access and refresh tokens will be exposed in the same way all the time. I've used sessions for years and they seem like a decent mechanism for secure and user friendly access control. But a session cannot create new sessions by itself. A refresh token on the other hand (if compromised) can create unlimited access tokens for the life of the refresh token. This scenario seems slightly more dangerous than a session. But at least with a refresh token, you can black list them unlike access tokens. So my thought was that maybe refresh tokens are stored in the session and are not transmitted. But this seems like it's getting overly complicated and maybe I am thinking about it all wrong. To sum my problem up, I want a web app to use the same api with a stateful firewall and for the mobile app, I want to use JWT tokens (access+refresh) with a stateless firewall without having to use different routes. Maybe it's a poor understanding of the Symfony firewall that is tripping me up.

    I hope I'm making sense. Either way, I hope you can demonstrate a reasonably secure and user friendly access control pattern that allows both web apps and mobile apps to share the same API.

    As a side note, most of your tutorials do a great job of addressing real world problems that go way beyond "hello world" apps, which I really appreciate. Thanks!

  • 2019-08-05 weaverryan

    ... we're thinking about it ... no promises yet, but we're getting this question a LOT :)

  • 2019-08-05 weaverryan

    Hi alsbury !

    We've started releasing this course (🎆) but we're still planning some of the details to cover in the second half of it. You're correct that storing tokens in a browser is not a good idea (storing any tokens in JavaScript opens you to XSS attacks). We'll talk about some of this, but the easiest way to authenticate (especially if you're own JavaScript is the consumer of the API) is with normal cookie-based session authentication. If you want the user to stay, sort of, "permanently" logged in, you'd do that with a remember me cookie (yes, the same boring remember me cookie as always)... though I think implementing that with the json_login mechanism we're using might require some extra work. I also want to mention one more important thing: using JWT (or any token) IS safe if you set it on an HttpOnly cookie on authentication success (instead of returning it so that JavaScript can store it). With an HttpOnly cookie, the token will be sent with all future requests, but won't be readable by JavaScript (so no XSS). And finally, any time you rely on a cookie being sent for authentication (whether that cookie contains the session id or a JWT) you're vulnerable to CSRF attacks unless you use SameSite cookies... *another* topic we'll talk about early in the tutorial :).

    Let me know what confusions you might still have - and we'll do our best to clear them up in the course!


  • 2019-08-05 Manoj Kumar

    Is there a chance to cover OAuth2 in this series ?

  • 2019-07-24 alsbury

    I really hope you guys cover how to securely store JWT tokens (primarily refresh tokens). I'd like the API I am creating to work for web clients and mobile devices. Most examples I've found regarding JWT tokens don't come close to dealing with JWT in production IMO. Storing refresh tokens in browser seems to be a bad pattern. Thought that sessions was the only safe way to leverage refresh tokens without re-typing passwords for web based clients if page is reloaded. Looking forward to the course. Hope you guys can clear up my confusion in this course.

  • 2019-07-16 ElGovanni

    Thanks for your answer, waitin for it!

  • 2019-07-16 Victor Bocharsky

    Hey ElGovanni ,

    Thank you for your interest in SymfonyCasts and this topic! We do really want to release a course about ElasticSearch someday, but there're a lot of good things that we're working on and would like to release sooner, so it's difficult to say when ElasticSearch course may be released. But yes, we have this topic in our idea pool :)

    Thank you for your patience!


  • 2019-07-15 ElGovanni

    Sorry for off top but will you make something about elasticsearch?

  • 2019-07-01 Mo El

    I'm waiting on it. I would love to see this!

  • 2019-06-26 Alberto


  • 2019-06-26 Diego Aguiar

    Hey Alberto

    At the moment we are focused on the "Messenger" tutorial. It may take one month to start releasing this second part of the Api Platform tutorial.
    I'm sorry for any inconveniences it may cause to you but there is just too much to cover up :)


  • 2019-06-25 Alberto

    when starts?

  • 2019-06-21 Jérôme 

    I look forward watching these tutorials!

  • 2019-06-16 Alex

    Can't wait! <3 <3 <3

  • 2019-06-06 mouad err

    Will be a great course, thank you very much

  • 2019-06-06 Fabio Restrepo

    Great!, I would only add advanced topics such as mercure protocol and CQRS. Thanks