If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeYa hemos hablado mucho de la autenticación: el proceso de inicio de sesión. Y... incluso ya hemos iniciado la sesión. Así que vamos a echar nuestro primer vistazo a la autorización, que es la parte divertida en la que podemos ir de un lado a otro y denegar el acceso a diferentes partes de nuestro sitio.
La forma más fácil de expulsar a alguien de tu fiesta es en realidad dentro deconfig/packages/security.yaml
. Es a través de access_control
:
security: | |
... lines 2 - 38 | |
# Easy way to control access for large sections of your site | |
# Note: Only the *first* access control that matches will be used | |
access_control: | |
# - { path: ^/admin, roles: ROLE_ADMIN } | |
# - { path: ^/profile, roles: ROLE_USER } |
Descomenta la primera entrada:
security: | |
... lines 2 - 40 | |
access_control: | |
- { path: ^/admin, roles: ROLE_ADMIN } | |
# - { path: ^/profile, roles: ROLE_USER } |
El path
es una expresión regular. Así que esto dice básicamente
Si una URL empieza por
/admin
-por tanto,/admin
o/admin*
-, entonces denegaré el acceso a menos que el usuario tengaROLE_ADMIN
.
Hablaremos más sobre los roles en un minuto... pero puedo decirte que nuestro usuario no tiene ese rol. Así que... vamos a intentar ir a una URL que coincida con esta ruta. En realidad tenemos una pequeña sección de administración en nuestro sitio. Asegúrate de que estás conectado... y luego ve a /admin
. ¡Acceso denegado! Se nos expulsa con un error 403.
En producción, puedes personalizar el aspecto de esta página de error 403... además de personalizar la página de error 404 o 422.
Hablemos de estos "roles". Abre la clase User
:src/Entity/User.php
. Así es como funciona. En el momento en que nos conectamos, Symfony llama a este método getRoles()
, que forma parte de UserInterface
:
... lines 1 - 12 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
... lines 15 - 78 | |
/** | |
* @see UserInterface | |
*/ | |
public function getRoles(): array | |
{ | |
$roles = $this->roles; | |
// guarantee every user at least has ROLE_USER | |
$roles[] = 'ROLE_USER'; | |
return array_unique($roles); | |
} | |
... lines 90 - 154 | |
} |
Devolvemos un array con los roles que debe tener este usuario. El comando make:user
generó esto para que siempre tengamos un rol llamado ROLE_USER
... más cualquier rol extra almacenado en la propiedad $this->roles
. Esa propiedad contiene una matriz de cadenas... que se almacenan en la base de datos como JSON:
... lines 1 - 12 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
... lines 15 - 26 | |
/** | |
* @ORM\Column(type="json") | |
*/ | |
private $roles = []; | |
... lines 31 - 154 | |
} |
Esto significa que podemos dar a cada usuario tantos roles como queramos. Hasta ahora, cuando hemos creado nuestros usuarios, no les hemos dado ningún rol... por lo que nuestra propiedad roles
está vacía. Pero gracias a cómo está escrito el método getRoles()
, cada usuario tiene al menos ROLE_USER
. El comando make:user
generó el código así porque todos los usuarios necesitan tener al menos un rol... de lo contrario vagan por nuestro sitio como usuarios zombis medio muertos. No es... bonito.
Así que, por convención, siempre damos a un usuario al menos ROLE_USER
. Ah, y la única regla sobre los roles -eso es un trabalenguas- es que deben empezar por ROLE_
. Más adelante en el tutorial, aprenderemos por qué.
En cualquier caso, en el momento en que nos conectamos, Symfony llama a getRoles()
, nos devuelve el array de roles, y los almacena. De hecho, podemos ver esto si hacemos clic en el icono de seguridad de la barra de herramientas de depuración de la web. ¡Sí! Roles: ROLE_USER
.
Entonces, cuando vamos a /admin
, esto coincide con nuestra primera entrada access_control
, comprueba si tenemos ROLE_ADMIN
, no lo tenemos, y deniega el acceso.
Ah, pero hay un detalle importante que hay que saber sobre access_control
: sólo se encontrará una coincidencia en una petición.
Por ejemplo, supón que tienes dos controles de acceso como éste:
security:
# ...
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/admin/foo, roles: ROLE_USER }
Si fuéramos a /admin
, eso coincidiría con la primera regla y sólo utilizaría la primera regla. Funciona como el enrutamiento: recorre la lista de control de acceso de uno en uno y, en cuanto encuentra la primera coincidencia, se detiene y utiliza sólo esa entrada.
Esto nos ayudará más adelante, cuando neguemos el acceso a toda una sección excepto a una URL. Pero por ahora, ¡sólo tenlo en cuenta!
Y... eso es todo. Los controles de acceso nos proporcionan una forma realmente sencilla de asegurar secciones enteras de nuestro sitio. Pero es sólo una forma de denegar el acceso. Pronto hablaremos de cómo podemos denegar el acceso controlador por controlador, algo que me gusta mucho.
Pero antes de hacerlo, sé que si intento acceder a esta página sin ROLE_ADMIN
, obtengo el error 403 prohibido. ¿Pero qué pasa si intento acceder a esta página como usuario anónimo? Ve a /logout
? Ahora no estamos conectados.
Vuelve a /admin
y... ¡whoa! ¡Un error!
Se requiere una autentificación completa para acceder a este recurso.
A continuación, vamos a hablar del "punto de entrada" de tu cortafuegos: la forma en que ayudas a los usuarios anónimos a iniciar el proceso de acceso.
Hmmm, when I go to /admin I see in web debug toolbar that I'm logged out.
But when I go to the other pages on the site I'm still logged in.
Hey Ruslan I.!
Let's see if we can figure this out! I don't immediately see the problem, but I do notice a few things:
1) From your debug:router, it looks like, indeed, there is no route for /admin
. So the problem isn't security or access_control... just that, somehow, the admin route isn't being seen! This page isn't something we built in this tutorial, but it should be included (it is, I just double-checked) in the "start/" directory of the code download. It's src/Controller/AdminController.php, and the Route annotation is above the dashboard()
method. Do you have this file & method? What does it look like? I'm sure that the root of the issue is centered, somehow, around this routing being missing... or something weird happening.
2) You mentioned that when you go to /admin (the 404 page) you are logged out. But you ARE logged in on other pages. This is unrelated to your problem and is due to a "quirk" in Symfony. In Symfony, if you hit a 404 page, even if you ARE logged in on other pages, you will NOT be logged in on the error page. The reason is that the "listener" that actually "logs you in" at the start of every request runs after the routing system. So the routing system says "Ah! Route not found" and triggers the error page... before the authentication system ever has a chance to log you in. You can actually see this right here on SymfonyCasts: try going to some invented URL on SymfonyCasts and check the upper right: you'll see that you "appear" to not be logged in. It's an annoying quirk of Symfony.
Let me know what you discover!
Cheers!
Oh, I'm sorry, I'm idiot.
1) I thought that /admin page is some sort of basic Symfony's built in dashboard and tried to understand what's wrong with my bundles and configs.
But that's just my bad, because I'm not able to download course code due to payment issues that I already discussed with Victor Bocharsky. I use the same project building it step by step from first tutorial so I have no AdminController. I will try to continue this course as much as I can.
Thank you for quick reply, I was waiting for help. I appreciate that.
2) Hm. Thanks for this fact, it's really interesting. Now I know a little bit more.
Hey Ruslan I.!
> But that's just my bad, because I'm not able to download course code due to payment issues that I already discussed with Victor Bocharsky.
Ah, sorry about that! In that case, don't feel bad - I'm glad we could get this sorted out. The /admin page is just a basic, normal Symfony controller that renders a pretty boring page (just so we have a functional page at /admin).
> 2) Hm. Thanks for this fact, it's really interesting. Now I know a little bit more.
Awesome :)
Cheers!
Hi, I'm facing a weird situation. When a logout from any page and then hit the back button (from the browser), i'm still able to see the last page until i refresh the page.
Hey discipolat!
Unfortunately... that's not so weird. It is, for better or worse, just how browsers work! Try it on some other site: log out, then click the back button. You'll go back to the previous page. This is because, when you hit back, there is no network request made to the site: your browser simply displays the previous page from cache.
If this is a problem, you could probably write some JavaScript that executes ever few seconds and that checks to see if the user is still authenticated. If they are not, you refresh the page via JavaScript.
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.3", // v3.3.0
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"doctrine/annotations": "^1.0", // 1.13.2
"doctrine/doctrine-bundle": "^2.1", // 2.6.3
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
"doctrine/orm": "^2.7", // 2.10.1
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.1
"pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
"pagerfanta/twig": "^3.3", // v3.3.0
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"scheb/2fa-bundle": "^5.12", // v5.12.1
"scheb/2fa-qr-code": "^5.12", // v5.12.1
"scheb/2fa-totp": "^5.12", // v5.12.1
"sensio/framework-extra-bundle": "^6.0", // v6.2.0
"stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
"symfony/asset": "5.3.*", // v5.3.4
"symfony/console": "5.3.*", // v5.3.7
"symfony/dotenv": "5.3.*", // v5.3.8
"symfony/flex": "^1.3.1", // v1.17.5
"symfony/form": "5.3.*", // v5.3.8
"symfony/framework-bundle": "5.3.*", // v5.3.8
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/property-access": "5.3.*", // v5.3.8
"symfony/property-info": "5.3.*", // v5.3.8
"symfony/rate-limiter": "5.3.*", // v5.3.4
"symfony/runtime": "5.3.*", // v5.3.4
"symfony/security-bundle": "5.3.*", // v5.3.8
"symfony/serializer": "5.3.*", // v5.3.8
"symfony/stopwatch": "5.3.*", // v5.3.4
"symfony/twig-bundle": "5.3.*", // v5.3.4
"symfony/ux-chartjs": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.8
"symfony/webpack-encore-bundle": "^1.7", // v1.12.0
"symfony/yaml": "5.3.*", // v5.3.6
"symfonycasts/verify-email-bundle": "^1.5", // v1.5.0
"twig/extra-bundle": "^2.12|^3.0", // v3.3.3
"twig/string-extra": "^3.3", // v3.3.3
"twig/twig": "^2.12|^3.0" // v3.3.3
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.3.*", // v5.3.4
"symfony/maker-bundle": "^1.15", // v1.34.0
"symfony/var-dumper": "5.3.*", // v5.3.8
"symfony/web-profiler-bundle": "5.3.*", // v5.3.8
"zenstruck/foundry": "^1.1" // v1.13.3
}
}
It's strange, when I go to /admin it says "no route found (404)" :(
No matter whether I uncommented the first line of access control in security.yaml or not.
And I'm logged in. When I logged out it's the same.
My Symfony version is 5.3 (all bundles have 5.3.* in composer.json, but by some reason web debug toolbar says 5.4.7)
php 7.4.28.
Windows
Result of symfony console debug:router