¿Documentos API en producción?
Bienvenidos de nuevo, maravillosos devotos de JSON, al episodio 2 de API Platform. En la parte 1, ¡nos pusimos manos a la obra! Creamos una API bastante asesina para almacenar tesoros de dragón, pero... ¡nos olvidamos por completo de añadir seguridad! Cualquier criatura pequeña y de pies peludos podría colarse por una puerta trasera... ¡y no tendríamos ni idea! Así que esta vez hablaremos de todo lo relacionado con la seguridad. Como la autenticación: ¿debo utilizar una sesión con un formulario de inicio de sesión... o necesito tokens de API? Y autorización, como denegar el acceso a rutas completas. Luego entraremos en cosas más complicadas, como mostrar u ocultar resultados en función del usuario e incluso mostrar u ocultar determinados campos en función del usuario. También hablaremos de campos totalmente personalizados, del método HTTP PATCH y de la creación de un sistema de pruebas de la API que tus amigos estarán celosos.
Configuración del proyecto
Ya sabes lo que hay que hacer: para profundizar realmente en este tema, debes codificar conmigo. Descarga el código del curso de esta página. Después de descomprimirlo, encontrarás un directorio start/ con el mismo código que ves aquí. Abre este ingenioso archivo README.md y sigue todas las instrucciones de configuración.
Aquí abajo estoy iniciando el servidor web symfony. Así que iré a un terminal que ya esté dentro del proyecto y ejecutaré
symfony serve -d
para iniciar un servidor web local en segundo plano. Perfecto Mantendré pulsado Cmd y haré clic en esa URL para abrirla en mi navegador. ¡Hola Treasure Connect! Esta es la app que creamos en el episodio 1... aunque trabajamos exclusivamente en la API. Creamos rutas para tesoros, usuarios y la posibilidad de relacionarlos.
Esta página de inicio es totalmente nueva para el episodio 2. Es una pequeña aplicación Vue que construí. Tiene un formulario de inicio de sesión... pero aún no funciona: dependerá de nosotros darle vida.
¿Documentos interactivos en producción?
Ahora, antes de sumergirnos en la seguridad, una pregunta que me hacen a veces es:
Oye Ryan, los documentos interactivos son superguays... pero ¿podría ocultarlos en producción?
Si tu API es privada -sólo está pensada para tu JavaScript-, podría tener sentido porque no quieres dar a conocer tus rutas al mundo. Sin embargo, no me siento demasiado obligado a ocultar los documentos... porque aunque lo hagas, las rutas siguen existiendo. Así que necesitarás una seguridad adecuada de todos modos.
Pero sí, ocultarlos es posible, así que veamos cómo. Aunque muestres tus docs, éste es un proceso interesante que muestra cómo funcionan juntas varias partes del sistema.
Busca tu terminal y ejecuta:
php ./bin/console config:dump api_platform
Recuerda: este comando muestra toda la configuración posible para API Platform. Veamos... busca "swagger". Ya está. Hay una sección con cosas comoenable_swagger, enable_swagger_ui, enable_re_doc, enable_entrypoint, yenable_docs. ¿Qué significa todo eso?
Hola ReDoc
Primero quiero enseñarte qué es ReDoc, porque no hablamos de ello en el primer tutorial. Actualmente estamos viendo la versión Swagger de nuestra documentación. Pero existe un formato competidor llamado ReDoc... ¡y puedes hacer clic en el enlace "ReDoc" de la parte inferior para verlo! ¡Sí! Es la misma información de la documentación... ¡pero con un diseño diferente! Si te gusta esto, está ahí para ti.
Desactivar los Docs
De todas formas, volviendo al terminal, hay un montón de configuraciones de "habilitación". Todas están relacionadas... pero son ligeramente diferentes. Por ejemplo, enable_swagger se refiere en realidad a la documentación de OpenAPI. Recuerda que es el documento JSON que alimenta los documentos de las API Swagger y ReDoc. Entonces, estos son si queremos mostrar o no esos dos tipos de documentación frontales. Y aquí abajo, enable_entrypoint y enable_docscontrolan si ciertas rutas se añaden o no a nuestra aplicación.
Apuesto a que no ha tenido mucho sentido, así que vamos a jugar con esto. Imagina que queremos desactivar la documentación por completo. De acuerdo Abre config/packages/api_platform.yamly, para empezar, añade enable_docs: false:
| api_platform: | |
| // ... lines 2 - 7 | |
| enable_docs: false |
En cuanto lo hagas y actualices... ¡bien! La documentación de nuestra API ha desaparecido... pero con un error 500. Cuando enable_docs: false, elimina literalmente la ruta a nuestra documentación.
Retrocedamos. Ir a /api siempre fue una especie de atajo para llegar a la documentación. La ruta real era /api/docs, /api/docs.json o .jsonld. Y ahora todas son 404 porque hemos desactivado esa ruta. Así que, ¡viva nuestra documentación!
Sin embargo, cuando vas a /api, en realidad no es una página de documentación. Es lo que se conoce como "punto de entrada": es nuestra página de inicio de la API. Esta página sigue existiendo... pero intenta enlazar con nuestra documentación de la API... que no existe, y explota.
Para desactivar el punto de entrada, muévete y añade enable_entrypoint: false:
| api_platform: | |
| // ... lines 2 - 8 | |
| enable_entrypoint: false |
Ahora yendo a /api nos da... ¡hermoso! A 404.
Vale, ya sabemos que podemos ir a /api/treasures.json o a .jsonld. Pero, ¿y si vamos a /api/treasures? Eso... ¡desgraciadamente es un error 500! Cuando nuestro navegador hace una petición, envía una cabecera Accept que dice que queremos HTML. Así que estamos pidiendo a nuestra API la versión html de los tesoros. Y la versión htmles... la documentación. Así que intenta enlazar con la documentación y explota.
Para desactivar esto, podemos comunicar al sistema que no tenemos Swagger ni documentación de la API en absoluto... para que deje de intentar enlazar con ella. Hazlo configurandoenable_swagger: false:
| api_platform: | |
| // ... lines 2 - 9 | |
| enable_swagger: false |
Aunque... eso sólo da lugar a otro error 500 que dice:
¡Eh, no puedes activar Swagger UI sin activar Swagger!
Arregla eso con enable_swagger_ui: false:
| api_platform: | |
| // ... lines 2 - 10 | |
| enable_swagger_ui: false |
Y ahora... ¡más cerca!
Deshabilitar el formato HTML
No se admite la serialización para el formato
html.
El problema es que seguimos solicitando la versión html de este recurso. Pero ahora que no tenemos documentación, nuestra API es como:
Um... no estoy muy seguro de cómo devolver una versión HTML de esto.
Y la verdad es: si desactivamos totalmente nuestra documentación, ¡ya no necesitamos un formato HTML! Y, por tanto, podemos desactivarlo. Hazlo, muy sencillamente, eliminando html deformats:
| api_platform: | |
| formats: | |
| jsonld: [ 'application/ld+json' ] | |
| json: [ 'application/json' ] | |
| jsonhal: [ 'application/hal+json' ] | |
| // ... lines 7 - 10 |
Y... en realidad tenemos otro punto donde necesitamos hacerlo: ensrc/Entity/DragonTreasure.php. Cuando añadimos nuestro formato personalizado csv... veámoslo aquí... repetimos todos los formatos, incluido html. Así que quita también html de ahí:
| // ... lines 1 - 26 | |
| ( | |
| // ... lines 28 - 40 | |
| formats: [ | |
| 'jsonld', | |
| 'json', | |
| 'jsonhal', | |
| 'csv' => 'text/csv', | |
| ], | |
| // ... lines 47 - 53 | |
| ) | |
| // ... lines 55 - 72 | |
| class DragonTreasure | |
| { | |
| // ... lines 75 - 232 | |
| } |
Cuando actualicemos ahora... ¡ya está! Como no hay formato HTML, se pone por defecto JSON-LD. Nuestros documentos están ahora totalmente desactivados.
Ah, y para desactivar los documentos sólo para producción, crearía una variable de entorno -como ENABLE_API_DOCS - y luego haría referencia a ella en mi configuración:
# config/packages/api_platform.yaml
api_platform:
enable_swagger_ui: '%env(bool:ENABLE_API_DOCS)%'
Pero... Me gusta la documentación, así que voy a deshacer este cambio... y este cambio también para recuperar nuestros documentos.
| api_platform: | |
| formats: | |
| jsonld: [ 'application/ld+json' ] | |
| json: [ 'application/json' ] | |
| html: [ 'text/html' ] | |
| jsonhal: [ 'application/hal+json' ] | |
| # enable_docs: false | |
| # enable_entrypoint: false | |
| # enable_swagger: false | |
| # enable_swagger_ui: false |
| // ... lines 1 - 26 | |
| ( | |
| // ... lines 28 - 40 | |
| formats: [ | |
| 'jsonld', | |
| 'json', | |
| 'html', | |
| 'jsonhal', | |
| 'csv' => 'text/csv', | |
| ], | |
| // ... lines 48 - 54 | |
| ) | |
| // ... lines 56 - 73 | |
| class DragonTreasure | |
| { | |
| // ... lines 76 - 233 | |
| } |
¡Me encanta!
A continuación, vamos a tener una charla informal sobre la autenticación. Tienes una API elegante: ¿necesitas tokens de API? ¿O algo más?
22 Comments
This is a minor point but might help someone out. On MacOS Docker v4.0 and later, the
docker-composecommand is now integrated into the CLI. So instead of runningdocker-compose up -dyou rundocker composer up -d. Not sure if the same change was made on Windows. Maybe someone can add that note to the README.md file. Thx!Hey @CDesign ,
Thanks for this tip! Yeah, we even have a note somewhere in the course about it :)
Cheers!
Hello,
I've got a problem here. I downloaded a source code, installed composer and npm, but after symfony serve I've got that kind of error:
Attempted to load class "Locale" from the global namespace.<br />Did you forget a "use" statement for "Symfony\Component\Validator\Constraints\Locale"?The solution is to install intl extension and to enable it in your php.ini :) Hope it will help anyone who will issues the same issue in the future, thanks
Hey sukhoy,
Yes, you're right, that error confuses more than it helps. Thanks for sharing your solution!
About environment varibale to disable docs (at 7:56).
As far as i know it is not possible for now, as
api_platformconfig loaded during a compile time. So it is has not picked up ENV variables, and next configuration does not work:My current sollution, is:
So if you know the solution how to do that with Environment varibales. Please share.
Hey @Oleh-K!
Ah, you're right! I was assuming too much! The APIPlatform Symfony integration references those variables at compile time (like you said). That would need to be fixed inside API Platform, but actually, I think it's not reasonably fixable. By setting, for example,
enable_swaggertofalse, a set of services is removed from the container (which is good - no reason to have those extra service around if they're not used). It would be a decent bit of work to allow this config to use environment variables, and the end result is that people who do want to disable them would still have these extra services "available" in the container, even though they're not used. Not a huge deal, but also probably a good reason not to "fix it".Anyway, I appreciate you pointing this out! I'll add a note to the course.
Cheers!
Anyway, thank you very much! I've found many helpful things in your course.
Is there a way to show an endpoint only to a specific role?
Hey @ToNy
Yes, you can check for the user's role inside your controller's method, or you can use the
#[IsGranted()]attribute. Reference to the docs: https://symfony.com/doc/current/security.html#security-securing-controller-attributesCheers!
Thanks for your reply! I'm affraid that Iphrased my question poorly, so I'll start again. I'm protecting the /api route using access_control in security.yaml, so only authenticated users can access the docs. I want to know if, instead of showing all the endpoints in the docs (swagger or redoc), show only the ones that the user has access to, based on the user roles and the security of the entity declared in the ApiResource annotation.
Hey @ToNy!
I think I understand and it seems reasonable. But it can be tricky to pull off. We talk about something kind of similar in an API Platform 2 tutorial - https://symfonycasts.com/screencast/api-platform2-security/resource-metadata-factory
There, we are trying to add some extra serialization groups in a way that our API docs will notice. You may be able to extend this to dynamically add groups based on the currently-authenticated user.
Another option might be to decorate and customize the
OpenApiFactory- perhaps removing resources based on the current user- https://symfonycasts.com/screencast/api-platform-security/open-api-decorationLet me know if any of these are successful.
Cheers!
Apart from the fact that I need to undo the changes made at the end. Did you notice that the frontend doesn't work with the documentation disabled?
An exception has been thrown during the rendering of a template ("Unable to generate a URL for the named route "api_entrypoint" as such route does not exist.").
Hey @Jamezs!
I believe you're referring to the homepage of the app, right? If so, yea, that's expected - but that's just due to how I built the app :). That custom homepage I built references the
api_entrypointroute so that it can link to the API homepage. So in a real app, if you disabled the docs, as long as you didn't try to link to this route, you'd be good ;).Cheers!
Hello!
I have a question about how you did the initial login, since it appears created directly in the video, are you creating it through make:controller login? If not, could I create it this way?
Hey @Fran!
At the start of this tutorial, the frontend and login stuff is pretty simple. It's a
MainController::homepage()that rendersmain/homepage.html.twig(and that controller is dead-simple). The template itself is pretty much empty except that it renders a Vue app, which renders the login form. All of that was created by hand. The controller and template and very simple and small (you could definitely usemake:controllerto get this), but I created the Vue app by hand.In a normal Symfony app, I would probably build my login form directly in PHP & Twig. For that, I'd use
make:authand choose the "login form" option. That will generate you the controller & template for the login form + a few other things.Does that help? Let me know!
Cheers!
Short question, the editor in use is Phpstrorm? Is this a special skin?
Hey @Klaus-M
Yes, it is PhpStorm, and I believe Ryan uses the "Atom Material Icons" plugin to enhance his editor's theme
Cheers!
Yes, that's it. Thank you very much.
Cheers!
I also ask the same question here, part 2 ok and what would part 1 be ? Thank you
Hey @pasquale_pellicani ,
I'm not sure I understand the question :)
What who else is asking here and what exactly? I see only your comment on this page :)
But in case you're looking for part 1, here it is: https://symfonycasts.com/screencast/api-platform .
Cheers!
Thank you very much, it would be ideal to change the title of the courses with part 1 and part 2 because I had difficulty understanding the order of the two courses. I take the opportunity to thank you for the excellent work done ;)
Hey @pasquale_pellicani ,
I see, yeah, it might not be clear enough. I'd try to add cross-link to this course. Also, you can always can look at the track to find the related courses and the correct order: https://symfonycasts.com/tracks/rest#api-platform-3
I hope this helps!
Cheers!
"Houston: no signs of life"
Start the conversation!