Probar la autenticación por token
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login Subscribe¿Qué tal una prueba como ésta... pero en la que iniciamos sesión con una clave API? Creemos un nuevo método: función pública testPostToCreateTreasureWithApiKey()
:
// ... lines 1 - 10 | |
class DragonTreasureResourceTest extends ApiTestCase | |
{ | |
// ... lines 13 - 61 | |
public function testPostToCreateTreasureWithApiKey(): void | |
{ | |
// ... lines 64 - 70 | |
} | |
} |
Esto empezará más o menos igual que antes. Copiaré la parte superior de la prueba anterior, quitaré el actingAs()
... y añadiré un dump()
cerca de la parte inferior:
// ... lines 1 - 10 | |
class DragonTreasureResourceTest extends ApiTestCase | |
{ | |
// ... lines 13 - 61 | |
public function testPostToCreateTreasureWithApiKey(): void | |
{ | |
$this->browser() | |
->post('/api/treasures', [ | |
'json' => [], | |
]) | |
->dump() | |
->assertStatus(422) | |
; | |
} | |
} |
Así, como antes, estamos enviando datos no válidos y esperamos un código de estado 422.
Copia ese nombre de método, luego gira y ejecuta sólo esta prueba:
symfony php bin/phpunit --filter=testPostToCreateTreasureWithApiKey
Y... ninguna sorpresa: obtenemos un código de estado 401 porque no estamos autenticados.
Enviemos una cabecera Authorization
, pero una no válida para empezar. Pasa una claveheaders
configurada en una matriz con Authorization
y luego la palabra Bearer
y luego... foo
.
Esto debería seguir fallando:
symfony php bin/phpunit --filter=testPostToCreateTreasureWithApiKey
Y... ¡lo hace! Pero con un mensaje de error diferente: invalid_token
. ¡Qué bien!
Utilizar un código real
Para pasar un token real, tenemos que introducir un token real en la base de datos. Hazlo con $token = ApiTokenFactory::createOne()
:
// ... lines 1 - 12 | |
class DragonTreasureResourceTest extends ApiTestCase | |
{ | |
// ... lines 15 - 63 | |
public function testPostToCreateTreasureWithApiKey(): void | |
{ | |
$token = ApiTokenFactory::createOne([ | |
// ... line 67 | |
]); | |
// ... lines 69 - 79 | |
} | |
} |
¿Necesitamos controlar algún campo de esto? En realidad sí. Abre DragonTreasure
. Si nos desplazamos hacia arriba, la operación Post
requiere ROLE_TREASURE_CREATE
:
// ... lines 1 - 27 | |
( | |
// ... lines 29 - 30 | |
operations: [ | |
// ... lines 32 - 37 | |
new Post( | |
security: 'is_granted("ROLE_TREASURE_CREATE")', | |
), | |
// ... lines 41 - 49 | |
], | |
// ... lines 51 - 64 | |
) | |
// ... lines 66 - 83 | |
class DragonTreasure | |
{ | |
// ... lines 86 - 243 | |
} |
Cuando nos autenticamos a través del formulario de acceso, gracias a role_hierarchy
, siempre tenemos eso. Pero cuando utilizamos una clave API, para obtener ese rol, el token necesita el ámbito correspondiente.
Para asegurarnos de que lo tenemos, en la prueba, establece la propiedad scopes
enApiToken::SCOPE_TREASURE_CREATE
:
// ... lines 1 - 4 | |
use App\Entity\ApiToken; | |
// ... lines 6 - 12 | |
class DragonTreasureResourceTest extends ApiTestCase | |
{ | |
// ... lines 15 - 63 | |
public function testPostToCreateTreasureWithApiKey(): void | |
{ | |
$token = ApiTokenFactory::createOne([ | |
'scopes' => [ApiToken::SCOPE_TREASURE_CREATE] | |
]); | |
// ... lines 69 - 79 | |
} | |
} |
Ahora pasa esto a la cabecera: $token->getToken()
. Ah... y déjame arreglarscopes
: que debería ser una matriz:
// ... lines 1 - 12 | |
class DragonTreasureResourceTest extends ApiTestCase | |
{ | |
// ... lines 15 - 63 | |
public function testPostToCreateTreasureWithApiKey(): void | |
{ | |
$token = ApiTokenFactory::createOne([ | |
'scopes' => [ApiToken::SCOPE_TREASURE_CREATE] | |
]); | |
// ... line 69 | |
$this->browser() | |
->post('/api/treasures', [ | |
// ... line 72 | |
'headers' => [ | |
'Authorization' => 'Bearer '.$token->getToken() | |
] | |
]) | |
// ... lines 77 - 78 | |
; | |
} | |
} |
¡Creo que ya estamos listos! Ejecuta la prueba:
symfony php bin/phpunit --filter=testPostToCreateTreasureWithApiKey
Y... ¡ya está! ¡Vemos los bonitos 422 errores de validación!
Probar un token con un alcance incorrecto
Hagamos una prueba para asegurarnos de que no tenemos acceso si a nuestro token le falta este ámbito. Copia todo el método de prueba... y pégalo a continuación. LlámalotestPostToCreateTreasureDeniedWithoutScope()
.
Esta vez, cambia scopes
por otra cosa, como SCOPE_TREASURE_EDIT
. A continuación, ahora esperamos un código de estado 403:
// ... lines 1 - 12 | |
class DragonTreasureResourceTest extends ApiTestCase | |
{ | |
// ... lines 15 - 80 | |
public function testPostToCreateTreasureDeniedWithoutScope(): void | |
{ | |
$token = ApiTokenFactory::createOne([ | |
'scopes' => [ApiToken::SCOPE_TREASURE_EDIT] | |
]); | |
$this->browser() | |
->post('/api/treasures', [ | |
'json' => [], | |
'headers' => [ | |
'Authorization' => 'Bearer '.$token->getToken() | |
] | |
]) | |
->assertStatus(403) | |
; | |
} | |
} |
Esta vez, vamos a ejecutar todas las pruebas:
symfony php bin/phpunit
Y... ¡todo verde! Un 422 y luego un 403. Ve a eliminar los volcados de ambos puntos.
Por cierto, si utilizas mucho los tokens de la API en tus pruebas, pasar la cabecera Authorization
puede resultar molesto. Browser tiene una forma en la que podemos crear un objeto Browser personalizado con métodos personalizados. Por ejemplo, podrías añadir un método authWithToken()
, pasar un array de ámbitos, y entonces crearía ese token y lo pondría en la cabecera
$this->browser()
->authWithToken([ApiToken::SCOPE_TREASURE_CREATE])
// ...
;
Esto no funciona en absoluto ahora mismo, pero consulta la documentación de Browser para aprender cómo hacerlo.
Siguiente: en la API Platform 3.1, el comportamiento de la operación PUT
está cambiando. Hablemos de cómo, y de lo que tenemos que hacer en nuestro código para prepararnos para ello.
So, my app has two firewalls configured. One for the API called "api" (using LexikJWTAuthenticationBundle) and one for the Web-Admin-Backend ("main") using "knpuniversity/oauth2-client-bundle" in combination with "stevenmaguire/oauth2-keycloak".
That means, that different from Chtioui's config, the API-Tokens are created externally, meaning that in my TestClass inside the setUp() function I do the following:
And in the test function that test the secured API-Endpoint, I can do the following:
But now I also wanted to test a web-route, but that somehow is not possible.
If I try:
I get a 500 response, an in the dump from the template it says:
The profileUrl is set in the KeycloakAuthenticator, in which a PostAuthenticationToken is set, which is why I also create one of these in the setup cuntion with the Admin-User I created in the fixtures.
Then I try to do the following:
But that does not seem to work, because my app still thinks, that I am logged out.
Any ideas on what I am doing wrong?
Thanks in advance.