amLoggedInAs with symfony 7.0 does not work with the acces token handler
symfony changed their security again. the code to do the automated login looks like that:
https://symfony.com/doc/7.0/security.html#login-programmatically
// get the user to be authenticated
$user = ...;
// log the user in on the current firewall
$security->login($user);
// if the firewall has more than one authenticator, you must pass it explicitly
// by using the name of built-in authenticators...
$security->login($user, 'form_login');
// ...or the service id of custom authenticators
$security->login($user, ExampleAuthenticator::class);
// you can also log in on a different firewall...
$security->login($user, 'form_login', 'other_firewall');
// ...and add badges
$security->login($user, 'form_login', 'other_firewall', [(new RememberMeBadge())->enable()]);
// use the redirection logic applied to regular login
$redirectResponse = $security->login($user);
return $redirectResponse;
having the following config:
security.yaml
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory:
memory:
users:
api_token:
password: <hashed password>.
roles: [ROLE_API]
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_in_memory
access_token:
token_handler: App\Security\AccessTokenHandler
token_extractors:
- 'header'
src/Security/AccessTokenHandler.php
<?php
namespace App\Security;
use Psr\Log\LoggerInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
class AccessTokenHandler implements AccessTokenHandlerInterface
{
final public const TOKEN_NAME = 'api_token';
public function __construct(
private UserProviderInterface $userProvider,
private UserPasswordHasherInterface $passwordHasher,
private LoggerInterface $logger,
) {
}
public function getUserBadgeFrom(?string $accessToken): UserBadge
{
if (null === $accessToken || !$this->verifyCredentials($accessToken)) {
throw new BadCredentialsException('Invalid credentials.');
}
return new UserBadge(self::TOKEN_NAME);
}
private function verifyCredentials(string $accessToken): bool
{
$user = $this->userProvider->loadUserByIdentifier(self::TOKEN_NAME);
if (get_class($user) !== InMemoryUser::class) {
$userClass = get_class($user);
$requiredUserClass = InMemoryUser::class;
$this->logger->error("Invalid User Class for access token authentication. Got $userClass but require $requiredUserClass");
return false;
}
return $this->passwordHasher->isPasswordValid($user, $accessToken);
}
}
version.html.twig
...
{% if is_granted('ROLE_API') %}
{# tried both this and the below code leads to the same #}
{% endif %}
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
App Version: {{ app_version }}
{% endif %}
...
cest
public function VersionIsVisibleForApiUsersCest(FunctionalTester $I): void
{
$user = new InMemoryUser(AccessTokenHandler::TOKEN_NAME, '<my dummy password>', ['ROLES_API']);
$I->amOnPage('/');
$I->amLoggedInAs($user, 'main');
$I->amOnPage('/version');
$I->see('App Version');
}
so the old code to autologin doesn't work any more or does not work with the acces token handler. the code from above fails silently, the user is simply not logged in (as far as i can verify).
switching to a custom code which uses the new security helper and its locin helper
cest:
$security = $I->grabService('test.security_helper');
$security->login($user);
services.yaml:
when@test:
services:
test.security_helper:
alias: security.helper
public: true
leads to
[Symfony\Component\Security\Core\Exception\LogicException] Unable to login without a request context.
i wanted to try to set the header for this request but only found https://github.com/Codeception/Codeception/issues/5308
any thoughts?
@c33s Do you think you can replicate this behavior in the test project (codeception/symfony-module-tests) ? This way I could investigate it.