In the new Symfony 6.1, I'm trying to implement authorization both by login-password and via github. I wrote my own UserProvider class for these purposes. And here is his method loadUserByIdentifier():
public function loadUserByIdentifier($username): UserInterface
{
$chunks = explode(':', $username);
file_put_contents("1985.txt", "\nUserProvider username: ".$username, FILE_APPEND);
if (\count($chunks) === 2 && $user = $this->users->findForAuthByNetwork($chunks[0], $chunks[1])) {
return $user; //
}
if ( ! $user = $this->users->findForAuthByEmail($username)) //
{
throw new IdentiferNotFoundException(''); // The exception is either NOT thrown, or the authenticate method does NOT catch it...
}
return $user;
}
In other words, if there is a ":" in $username, then the user login via github with a $username of the form "github:105277821". Otherwise it's an email.
I also implemented my own GithubAuthenticator class and here is its authenticate() method:
public function authenticate(Request $request): Passport
{
$client = $this->clientRegistry->getClient('github_main');
$accessToken = $this->fetchAccessToken($client);
return new SelfValidatingPassport(
new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {
$githubUser = $client->fetchUserFromToken($accessToken);
$network = "github";
$id = (string)$githubUser->toArray()["id"];
$username = $network.":".$id;
file_put_contents("1985.txt", "\nGithubAuthenticator: ".$username, FILE_APPEND);
$command = new Command($network, $id);
try {
return $this->userProvider->loadUserByIdentifier($username);
} catch (IdentiferNotFoundException $e) {
// ! ! ! I can't get HERE ! ! !
file_put_contents("1985.txt", "\nIdentiferNotFoundException thrown...", FILE_APPEND);
$this->handler->handle($command);
return $this->userProvider->loadUserByIdentifier($username);
}
})
);
}
IdentiferNotFoundException simply extends Exception (I tried putting UserNotFoundException instead - it doesn't solve the problem)
class IdentiferNotFoundException extends \Exception{}
And the problem is that when a new user login via github, an IdentiferNotFoundException exception from UserProvider should be thrown, but it does not throw, or the authenticate method does NOT catch it - it is not clear. And instead, for some unknown reason, the user logs in via email. Why is this happening and how to fix the situation, can someone explain?
I recorded some logs via file_put_contents and this is what I got:
GithubAuthenticator: github:107927341
UserProvider username: github:107927341
findForAuthByNetwork network: github:107927341
findForAuthByEmail Email: github:107927341
UserProvider username: admin@app.test
findForAuthByEmail Email: admin@app.test
That is, it can be seen that the Authenticator is called correctly, then it calls UserProvider, and UserProvider consistently calls 2 methods findForAuthByNetwork and findForAuthByEmail, but then the exception is not thrown. And for some unknown reason, he calls UserProvider again with an Email and logs the user in by mail. How can this be?
My security.yaml:
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
fetcher:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: fetcher
user_checker: App\Security\UserChecker
custom_authenticator:
- App\Security\LoginFormAuthenticator
- App\Security\OAuth\GithubAuthenticator
# entry_point: App\Security\LoginFormAuthenticator
logout:
path: app_logout
# where to redirect after logout
# target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
role_hierarchy:
ROLE_ADMIN:
- ROLE_USER
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
# Use PUBLIC_ACCESS instead of IS_AUTHENTICATED_ANONYMOUSLY
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/connect, roles: PUBLIC_ACCESS }
- { path: ^/signup, roles: PUBLIC_ACCESS }
- { path: ^/reset, roles: PUBLIC_ACCESS }
- { path: ^/logout, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER }
debug:autowiring --all
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
App\Container\Model\User\Services\ResetTokenizerFactory
App\Controller\Auth\AuthController
App\Controller\Auth\OAuth\GithubController
App\Controller\Auth\ResetController
App\Controller\HomeController
App\Controller\SignUpController
App\DataFixtures\UserFixture
App\Kernel (kernel)
App\Model\User\Entity\User\UsersRepository
App\Model\User\IdentiferNotFoundException
App\Model\User\Services\ConfirmTokenizer
App\Model\User\Services\Flusher
App\Model\User\Services\PasswordHasher
App\Model\User\Services\ResetTokenizer
App\Model\User\UseCase\Network\Auth\Command
App\Model\User\UseCase\Network\Auth\Handler
App\Model\User\UseCase\Reset\Request\Command
App\Model\User\UseCase\Reset\Request\Handler
App\Model\User\UseCase\Reset\Reset\Command
App\Model\User\UseCase\Reset\Reset\Form
App\Model\User\UseCase\Reset\Reset\Handler
App\Model\User\UseCase\SignUp\Confirm\Command
App\Model\User\UseCase\SignUp\Confirm\Handler
App\Model\User\UseCase\SignUp\Request\Command
App\Model\User\UseCase\SignUp\Request\Handler
App\ReadModel\User\AuthView
App\ReadModel\User\UserFetcher
App\Security\LoginFormAuthenticator
App\Security\OAuth\GithubAuthenticator
App\Security\UserChecker
App\Security\UserIdentity
App\Security\UserProvider
@internal
Doctrine\Bundle\DoctrineBundle\Controller\ProfilerController
Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider
Interface for annotation readers.
Doctrine\Common\Annotations\Reader (annotations.cached_reader)
Doctrine\Common\Persistence\ManagerRegistry (doctrine)
A database abstraction-level connection that implements features like events, transaction isolation levels, configuration, emulated transaction nesting, lazy connecting and more.
Doctrine\DBAL\Connection (doctrine.dbal.default_connection)
Doctrine\DBAL\Connection $defaultConnection (doctrine.dbal.default_connection)
Connection interface. Driver connections must implement this interface.
Doctrine\DBAL\Driver\Connection (doctrine.dbal.default_connection)
Task for executing arbitrary SQL that can come from a file or directly from the command line.
Doctrine\DBAL\Tools\Console\Command\RunSqlCommand
EntityManager interface
Doctrine\ORM\EntityManagerInterface (doctrine.orm.default_entity_manager)
Doctrine\ORM\EntityManagerInterface $defaultEntityManager (doctrine.orm.default_entity_manager)
Contract covering object managers for a Doctrine persistence layer ManagerRegistry class to implement.
Doctrine\Persistence\ManagerRegistry (doctrine)
KnpU\OAuth2ClientBundle\Client\ClientRegistry (knpu.oauth2.registry)
KnpU\OAuth2ClientBundle\Client\Provider\GithubClient (knpu.oauth2.client.github_main)
CacheItemPoolInterface generates CacheItemInterface objects.
Psr\Cache\CacheItemPoolInterface (cache.app)
Psr\Container\ContainerInterface $parameterBag (parameter_bag)
Defines a dispatcher for events.
Psr\EventDispatcher\EventDispatcherInterface (debug.event_dispatcher)
Psr\Http\Client\ClientInterface (psr18.http_client)
Describes a logger instance.
Psr\Log\LoggerInterface (monolog.logger)
Psr\Log\LoggerInterface $cacheLogger (monolog.logger.cache)
Psr\Log\LoggerInterface $consoleLogger (monolog.logger.console)
Psr\Log\LoggerInterface $debugLogger (monolog.logger.debug)
Psr\Log\LoggerInterface $deprecationLogger (monolog.logger.deprecation)
Psr\Log\LoggerInterface $doctrineLogger (monolog.logger.doctrine)
Psr\Log\LoggerInterface $eventLogger (monolog.logger.event)
Psr\Log\LoggerInterface $httpClientLogger (monolog.logger.http_client)
Psr\Log\LoggerInterface $mailerLogger (monolog.logger.mailer)
Psr\Log\LoggerInterface $messengerLogger (monolog.logger.messenger)
Psr\Log\LoggerInterface $phpLogger (monolog.logger.php)
Psr\Log\LoggerInterface $profilerLogger (monolog.logger.profiler)
Psr\Log\LoggerInterface $requestLogger (monolog.logger.request)
Psr\Log\LoggerInterface $routerLogger (monolog.logger.router)
Psr\Log\LoggerInterface $securityLogger (monolog.logger.security)
Psr\Log\LoggerInterface $translationLogger (monolog.logger.translation)
SessionHandlerInterface (session.handler.native_file)
Redirects a request to another URL.
Symfony\Bundle\FrameworkBundle\Controller\RedirectController
TemplateController.
Symfony\Bundle\FrameworkBundle\Controller\TemplateController
Helps manage asset URLs.
Symfony\Component\Asset\Packages (assets.packages)
ContainerBagInterface is the interface implemented by objects that manage service container parameters.
Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface (parameter_bag)
ParameterBagInterface is the interface implemented by objects that manage service container parameters.
Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface (parameter_bag)
Turns public and "container.reversible" services back to their ids.
Symfony\Component\DependencyInjection\ReverseContainer (reverse_container)
The EventDispatcherInterface is the central point of Symfony's event listener system. Listeners are registered on the manager and events are dispatched through the manager.
Symfony\Component\EventDispatcher\EventDispatcherInterface (debug.event_dispatcher)
Provides basic utility to manipulate the file system.
Symfony\Component\Filesystem\Filesystem (filesystem)
Allows creating a form based on a name, a class or a property.
Symfony\Component\Form\FormFactoryInterface (form.factory)
The central registry of the Form component.
Symfony\Component\Form\FormRegistryInterface (form.registry)
Creates ResolvedFormTypeInterface instances.
Symfony\Component\Form\ResolvedFormTypeFactoryInterface (form.resolved_type_factory)
Request stack that controls the lifecycle of requests.
Symfony\Component\HttpFoundation\RequestStack (request_stack)
A helper service for manipulating URLs within and outside the request scope.
Symfony\Component\HttpFoundation\UrlHelper (url_helper)
FileLocator uses the KernelInterface to locate resources in bundles.
Symfony\Component\HttpKernel\Config\FileLocator (file_locator)
Formats debug file links.
Symfony\Component\HttpKernel\Debug\FileLinkFormatter (debug.file_link_formatter)
Interface implemented by rendering strategies able to generate an URL for a fragment.
Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface (fragment.uri_generator)
Interface implemented by HTTP cache stores.
Symfony\Component\HttpKernel\HttpCache\StoreInterface (http_cache.store)
HttpKernelInterface handles a Request to convert it to a Response.
Symfony\Component\HttpKernel\HttpKernelInterface (http_kernel)
The Kernel is the heart of the Symfony system.
Symfony\Component\HttpKernel\KernelInterface (kernel)
Signs URIs.
Symfony\Component\HttpKernel\UriSigner (uri_signer)
Interface for mailers able to send emails synchronous and/or asynchronous.
Symfony\Component\Mailer\MailerInterface (mailer.mailer)
Interface for all mailer transports.
Symfony\Component\Mailer\Transport\TransportInterface (mailer.default_transport)
Symfony\Component\Messenger\MessageBusInterface (debug.traced.messenger.bus.default)
Symfony\Component\Messenger\Transport\Serialization\SerializerInterface (messenger.transport.native_php_serializer)
Guesses the MIME type of a file.
Symfony\Component\Mime\MimeTypeGuesserInterface (mime_types)
Symfony\Component\Mime\MimeTypesInterface (mime_types)
Interface for classes able to send chat messages synchronous and/or asynchronous.
Symfony\Component\Notifier\ChatterInterface (chatter)
Interface for the Notifier system.
Symfony\Component\Notifier\NotifierInterface (notifier)
Interface for classes able to send SMS messages synchronous and/or asynchronous.
Symfony\Component\Notifier\TexterInterface (texter)
PasswordHasherFactoryInterface to support different password hashers for different user accounts.
Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface (security.password_hasher_factory)
Interface for the user password hasher service.
Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface (security.user_password_hasher)
Writes and reads values to/from an object/array graph.
Symfony\Component\PropertyAccess\PropertyAccessorInterface (property_accessor)
Guesses if the property can be accessed or mutated.
Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface (property_info)
Guesses the property's human readable description.
Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface (property_info)
Gets info about PHP class properties.
Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface (property_info)
Guesses if the property can be initialized through the constructor.
Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface (property_info)
Extracts the list of properties available for the given class.
Symfony\Component\PropertyInfo\PropertyListExtractorInterface (property_info)
Extract read information for the property of a class.
Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface (property_info.reflection_extractor)
Type Extractor Interface.
Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface (property_info)
Extract write information for the property of a class.
Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface (property_info.reflection_extractor)
UrlGeneratorInterface is the interface that all URL generator classes must implement.
Symfony\Component\Routing\Generator\UrlGeneratorInterface (router.default)
UrlMatcherInterface is the interface that all URL matcher classes must implement.
Symfony\Component\Routing\Matcher\UrlMatcherInterface (router.default)
Holds information about the current request.
Symfony\Component\Routing\RequestContext (router.request_context)
Symfony\Component\Routing\RequestContextAwareInterface (router.default)
RouterInterface is the interface that all Router classes must implement.
Symfony\Component\Routing\RouterInterface (router.default)
The TokenStorageInterface.
Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface (security.token_storage)
AccessDecisionManagerInterface makes authorization decisions.
Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface (debug.security.access.decision_manager)
The AuthorizationCheckerInterface.
Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface (security.authorization_checker)
RoleHierarchyInterface is the interface for a role hierarchy.
Symfony\Component\Security\Core\Role\RoleHierarchyInterface (security.role_hierarchy)
Helper class for commonly-needed security tasks.
Symfony\Component\Security\Core\Security (security.helper)
Represents a class that loads UserInterface objects from some source for the authentication system.
Symfony\Component\Security\Core\User\UserProviderInterface (App\Security\UserProvider)
Manages CSRF tokens.
Symfony\Component\Security\Csrf\CsrfTokenManagerInterface (security.csrf.token_manager)
Generates CSRF tokens.
Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface (security.csrf.token_generator)
Stores CSRF tokens.
Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface (security.csrf.token_storage)
Extracts Security Errors from Request.
Symfony\Component\Security\Http\Authentication\AuthenticationUtils (security.authentication_utils)
Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface (security.user_authenticator)
Firewall uses a FirewallMap to register security listeners for the given request.
Symfony\Component\Security\Http\Firewall (debug.security.firewall)
This interface must be implemented by firewall maps.
Symfony\Component\Security\Http\FirewallMapInterface (security.firewall.map)
Encapsulates the logic needed to create sub-requests, redirect the user, and match URLs.
Symfony\Component\Security\Http\HttpUtils (security.http_utils)
SessionAuthenticationStrategyInterface.
Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface (security.authentication.session_strategy)
Symfony\Component\Serializer\Encoder\DecoderInterface (debug.serializer)
Symfony\Component\Serializer\Encoder\EncoderInterface (debug.serializer)
Knows how to get the class discriminator mapping for classes and objects.
Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface (serializer.mapping.class_discriminator_resolver)
Returns a {@see ClassMetadataInterface}.
Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface (serializer.mapping.class_metadata_factory)
Symfony\Component\Serializer\Normalizer\DenormalizerInterface (debug.serializer)
Symfony\Component\Serializer\Normalizer\NormalizerInterface (debug.serializer)
Converts between objects and arrays using the PropertyAccess component.
Symfony\Component\Serializer\Normalizer\ObjectNormalizer (debug.serializer.normalizer.object)
Converts between objects and arrays by mapping properties.
Symfony\Component\Serializer\Normalizer\PropertyNormalizer (serializer.normalizer.property)
Symfony\Component\Serializer\SerializerInterface (debug.serializer)
Stopwatch provides a way to profile code.
Symfony\Component\Stopwatch\Stopwatch (debug.stopwatch)
Creates a URL-friendly slug from a given string.
Symfony\Component\String\Slugger\SluggerInterface (slugger)
Extracts translation messages from a directory or files to the catalogue. New found messages are injected to the catalogue using the prefix.
Symfony\Component\Translation\Extractor\ExtractorInterface (translation.extractor)
Symfony\Component\Translation\LocaleSwitcher (translation.locale_switcher)
TranslationReader reads translation messages from translation files.
Symfony\Component\Translation\Reader\TranslationReaderInterface (translation.reader)
TranslationWriter writes translation messages.
Symfony\Component\Translation\Writer\TranslationWriterInterface (translation.writer)
Validates PHP values against constraints.
Symfony\Component\Validator\Validator\ValidatorInterface (debug.validator)
Covers most simple to advanced caching needs.
Symfony\Contracts\Cache\CacheInterface (cache.app)
Allows invalidating cached items using tags.
Symfony\Contracts\Cache\TagAwareCacheInterface (cache.app.taggable)
Allows providing hooks on domain-specific lifecycles by dispatching events.
Symfony\Contracts\EventDispatcher\EventDispatcherInterface (debug.event_dispatcher)
Provides flexible methods for requesting HTTP resources synchronously or asynchronously.
Symfony\Contracts\HttpClient\HttpClientInterface (.debug.http_client)
Symfony\Contracts\Translation\LocaleAwareInterface (translation.locale_switcher)
Symfony\Contracts\Translation\TranslatorInterface (translator.data_collector)
Stores the Twig configuration and renders templates.
Twig\Environment (twig)
Pro-tip: use interfaces in your type-hints instead of classes to benefit from the dependency inversion principle.