Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

¿Utilizar o no la autenticación por token de la API?

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Ésta es la pregunta del millón cuando se trata de la seguridad y las API: ¿necesita mi sitio algún tipo de autentificación por token de API? Es muy probable que la respuesta sea no. Incluso si tu aplicación tiene algunas rúbricas de API -como la nuestra-, si estás creando estas rúbricas únicamente para que tu propio JavaScript para tu propio sitio pueda utilizarlas, entonces no necesitas un sistema de autenticación por token de API. No, tu vida será mucho más sencilla si utilizas un formulario de acceso normal y una autenticación basada en la sesión.

La autenticación basada en la sesión es precisamente la razón por la que tenemos acceso a este punto final: nos hemos conectado previamente... y nuestra cookie de sesión se utiliza para autenticarnos. Esto funciona igual de bien en una página real que en un punto final de la API.

Para probarlo, antes de empezar el tutorial, he creado un controlador Stimulus llamado user-api_controller.js:

import { Controller } from 'stimulus';
import axios from 'axios';
export default class extends Controller {
static values = {
url: String
}
async connect() {
const response = await axios.get(this.urlValue);
console.log(response.data);
}
}

Es muy sencillo: hace una petición a la API... y registra el resultado. Vamos a utilizarlo para hacer una petición de API a /api/me para demostrar que las llamadas Ajax pueden acceder a las rutas autenticadas.

Para activar el controlador Stimulus, abre templates/base.html.twig... y encuentra el elemento body: ese es un lugar fácil para adjuntarlo: siis_granted('IS_AUTHENTICATED_REMEMBERED'), entonces {{ stimulus_controller() }}y el nombre: user-api:

... line 1
<html>
... lines 3 - 14
<body
{% if is_granted('IS_AUTHENTICATED_REMEMBERED') %}
{{ stimulus_controller('user-api', {
... line 18
}) }}
{% endif %}
>
... lines 22 - 85
</body>
</html>

Así, nuestro JavaScript será llamado sólo si estamos conectados. Para pasar la URL a la ruta, añade un segundo argumento con url establecido en path('app_user_api_me'):

... line 1
<html>
... lines 3 - 14
<body
{% if is_granted('IS_AUTHENTICATED_REMEMBERED') %}
{{ stimulus_controller('user-api', {
url: path('app_user_api_me')
}) }}
{% endif %}
>
... lines 22 - 85
</body>
</html>

Y me doy cuenta de que aún no he dado a nuestro punto final de la API un nombre de ruta... así que vamos a hacerlo:

... lines 1 - 7
class UserController extends BaseController
{
/**
* @Route("/api/me", name="app_user_api_me")
... line 12
*/
public function apiMe()
{
... lines 16 - 18
}
}

De nuevo en base.html.twig, ¡sí! Mi editor parece feliz ahora.

Vale, vuelvo a la página de inicio, inspecciono el elemento, voy a la consola y... ¡ahí están mis datos de usuario! La petición Ajax envía la cookie de sesión y así... la autenticación funciona.

Así que si lo único que necesita utilizar tu API es tu propio JavaScript, ahórrate un montón de problemas y utiliza simplemente un formulario de acceso. Y si quieres ponerte elegante y enviar tu inicio de sesión por medio de Ajax, puedes hacerlo perfectamente. De hecho, si usas Turbo, eso ocurre automáticamente. Pero si quieres escribir algún JavaScript personalizado, no hay problema. Sólo tienes que utilizar Ajax para enviar el formulario de inicio de sesión y la cookie de sesión se establecerá automáticamente de forma normal. Si decides hacer esto, el único ajuste que necesitarás es hacer que el autentificador del formulario de inicio de sesión devuelva JSON en lugar de redirigir. Yo probablemente volvería a utilizar mi LoginFormAuthenticator personalizado porque sería súper fácil devolver JSON desdeonAuthenticationSuccess():

... lines 1 - 26
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 29 - 66
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
... lines 69 - 75
}
... lines 77 - 81
}

Cuando sí necesitas tokens de la API

Entonces, ¿cuándo necesitamos un sistema de autenticación por token de API? La respuesta es bastante sencilla: si alguien que no sea el JavaScript de tu propio sitio necesita acceder a tu API... incluso si tu JavaScript vive en un dominio completamente diferente. Si te encuentras en esta situación, probablemente vas a necesitar algún tipo de sistema de tokens de API. Si necesitas OAuth o un sistema más sencillo... depende. No cubriremos los tokens de la API en este tutorial, pero creamos un sistema bastante bueno en nuestro tutorial Symfony 4 Security, que puedes consultar.

Siguiente: ¡vamos a añadir un formulario de registro!

Leave a comment!

¡Este tutorial también funciona muy bien para Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "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
    }
}