Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

amLoggedInAs with symfony 7.0 does not work with the acces token handler #186

Open
c33s opened this issue Mar 9, 2024 · 1 comment
Open

Comments

@c33s
Copy link

c33s commented Mar 9, 2024

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 Codeception/Codeception#5308

any thoughts?

@TavoNiievez
Copy link
Member

@c33s Do you think you can replicate this behavior in the test project (codeception/symfony-module-tests) ?
This way I could investigate it.

@TavoNiievez TavoNiievez changed the title amLoggedInAs with symfony 7.0 does not work amLoggedInAs with symfony 7.0 does not work with the acces token handler Apr 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants