Symfony - Setup the TokenController with LexikJWTAuthenticationBundle - symfony

I'm using LexikJWTAuthenticationBundle https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#installation
I'm setting up my controller to get a Token:
class TokenController extends AbstractController
{
/**
* #Route("/api/token", name="token", methods={"POST"})
* #param Request $request
* #param JWTEncoderInterface $JWTEncoder
* #return JsonResponse
* #throws \Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTEncodeFailureException
*/
public function token(Request $request, JWTEncoderInterface $JWTEncoder)
{
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy([
'email' => $request->getUser(),
]);
if (!$user) {
throw $this->createNotFoundException('User Not Found');
}
$isValid = $this->get('security.password_encoder')
->isPasswordValid($user, $request->getPassword());
if (!$isValid) {
throw new BadCredentialsException();
}
$token = $JWTEncoder->encode([
'email' => $user->getEmail(),
'exp' => time() + 3600 // 1 hour expiration
]);
return new JsonResponse(['token' => $token]);
}
}
But I have this error:
Service "security.password_encoder" not found: even though it exists
in the app's container, the container inside
"App\Controller\TokenController" is a smaller service locator that
only knows about the "doctrine", "form.factory", "http_kernel",
"parameter_bag", "request_stack", "router",
"security.authorization_checker", "security.csrf.token_manager",
"security.token_storage", "serializer", "session" and "twig" services.
Unless you need extra laziness, try using dependency injection
instead. Otherwise, you need to declare it using
"TokenController::getSubscribedServices()".
I have use Dependency injection and here is my service conf
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Where is the problem?

You are extending from AbstractController, using this controller, services that you access using $this->get() will be limited. To access the password encoder service you can inject Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface into your controller action or through the controller class constructor.
private $passwordEncoder;
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
...
$this->passwordEncoder->isPasswordValid()
...
Or you can extend from Symfony\Bundle\FrameworkBundle\Controller\Controller for full container access. $this->get('security.password_encoder') should work with this.

Related

Launch Authenticator process from controller in Symfony 5+

I'm trying to write a SSO authentication bundle for Symfony 5.4+
When I try to access pages with security attribute set to true in security.yaml, no problem, my authenticator class automatically launches and runs well. I can authenticated myself and then get a filled user instance in Symfony.
But now, what I'd like to do is a simple login page that would play the authentication process and then redirect the user to a page defined by the application admin in config files (for eventually add an authenticate button in app with public access).
My question is: how can I launch this authentication process from a controller as my login route has a public access (and then authentication system is never launched) ?
Something like that :
class AuthenticationController extends AbstractController
{
public function login(Request $request, Security $security)
{
if (!$this->getUser())
$security->pleaseLaunchTheAuthenticationProcess()
return new RedirectResponse('/my_path');
}
}
You can achieve that using the UserAuthenticatorInterface:
/**
* #Route("/some-route", name="some_route")
*
* #param Request $request
* #param UserAuthenticatorInterface $userAuthenticator
* #param AuthenticatorInterface $authenticator
* #return Response
*/
public function book(
Request $request,
UserAuthenticatorInterface $userAuthenticator,
AuthenticatorInterface $authenticator
): Response
{
if ($this->getUser() instanceof User) {
$userAuthenticator->authenticateUser($this->getUser(), $authenticator, $request);
}
return $this->redirectToRoute('/my_path');
}
You will have an error about AuthenticatorInterface which cannot be launched. You have to declare it manually in your service.yaml:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
$authenticator: '#security.authenticator.form_login.main'

Symfony autowiring Event Listener

Symfony 4.2.2
To cache all responses in one controller, I'm using an event listener for the kernel.controller event. My Event listener needs a couple of services and info:
EnityManagerInterface
the controller being cached
kernel cache folder
I have set this up like this:
namespace App\Listener;
use App\Controller\DataOutputController;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class CachedOutput{
protected $cacheFolder;
protected $em;
protected $controller;
public function __construct($cacheFolder, EntityManagerInterface $em, DataOutputController $controller )
{
$this->cacheFolder = $cacheFolder;
$this->em = $em;
$this->controller = $controller;
}
public function findCachedObject(FilterControllerEvent $event, $eventName, TraceableEventDispatcher $dispatcher
){
$params = $event->getRequest()->attributes->get('_route_params');
$fileType = $this->em->getRepository('App:FileType')->find($params->get('fileType'));
$dataSet = $this->controller->getDataSet($params->get('dataSetSearch')?:'latest', $fileType->getType());
$cacheFile = $this->cacheFolder.'/output/DS'.$dataSet->getId().'-FT'.$fileType->getId().'.html';
if (file_exists($cacheFile)){
$fh = fopen($cacheFile,'r');
return new Response(fpassthru($fh));
}
}
}
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: true # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
bind:
$projectDir: '%kernel.project_dir%'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
controller.return_cached_output:
class: App\Listener\CachedOutput
arguments:
$cacheFolder: "%kernel.cache_dir%"
tags:
- { name: kernel.event_listener, event: kernel.controller, method: findCachedObject }
However, I still get an error regarding the cache folder:
Cannot autowire service "App\Listener\CachedOutput": argument "$cacheFolder" of method "__construct()" has no type-hint, you should configure its value explicitly.
What am I missing?
Update:
Have tried to use alias for the service like this:
App\Listener\CachedOutput:
public: false
arguments:
$cacheFolder: "%kernel.cache_dir%"
tags:
- { name: kernel.event_listener, event: kernel.controller, method: findCachedObject }
return_cached_output:
alias: App\Listener\CachedOutput
public: true
with no success
You need to type-hint in your __construct(string $cacheFolder, ...)

How to inject Doctrine Entity Manager into Symfony 4 Service

I have a controller
use Doctrine\ORM\EntityManagerInterface:
class ExampleController{
public function someFunction(ExampleService $injectedService){
$injectedService->serviceFunction();
}
}
With a Service
use Doctrine\ORM\EntityManagerInterface;
class ExampleService{
public function __construct(EntityManagerInterface $em){
...
}
}
However, calls to someFunction() fail due to 0 parameters being passed (the EntityManagerInterface is not being injected). I am attempting to use the EntityManager from the Service. Autowiring is on. I've tried the solutions for Symfony3 but they don't seem to work unless I'm missing something.
Edit: Here is my services.yaml:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Use only in Symfony 4.
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Name; //if you use entity for example Name
class ExampleService{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
function newName($code) // for example with a entity
{
$name = new Name();
$name->setCode($code); // use setter for entity
$this->em->persist($name);
$this->em->flush();
}
}
I know it's an old post, but just in case somebody struggles with this, there's a typo in the use statment:
use Doctrine\ORM\EntityManagerInterface: //<- see that's a colon, not a semicolon
Agree with Yarimadam. Service container, dependency injection and autowiring is not a story about injecting into methods. Dependencies injected into objects we are calling "services".
When application is up, service container is built injecting one services into another ones via class constructor or "set" method invocation.
Your ExampleController::someFunction is intended to be called only by you, so only way how this method will receive $injectedService as an argument, is that you will pass it evidently. This is the wrong way.
A classic symfony service with autowiring uses constructor injection method to inject dependencies. In your case, you don't have a constructor.
You may consider to add a constructor method and set dependency to a private class property. And use accordingly.
Or you can utilize setter injection.
Service Configuration:
services:
app.example_controller:
class: Your\Namespace\ExampleController
calls:
- [setExampleService, ['#exampleService']]
Controller Class:
class ExampleController
{
private $exampleService;
public function someFunction() {
$this->exampleService->serviceFunction();
}
public function setExampleService(ExampleService $exampleService) {
$this->exampleService = $exampleService;
}
}

Symfony autowiring interface to a real service

I need to configure an autowiring for a Symfony 2.8 application.
A service has a dependency on a Symfony\Component\Templating\EngineInterface:
use Symfony\Component\Templating\EngineInterface;
class MyService
{
/** #var EngineInterface */
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
}
But I obviously get an error since this interface is implemented in three different classes.
Unable to autowire argument of type
"Symfony\Component\Templating\EngineInterface" for the service
"myservice". Multiple
services exist for th is interface (templating.engine.delegating,
templating.engine.php, templating.engine.twig).
I would like to inject the templating.engine.delegating every time when the EngineInterface is required so I add a definition to my service.yml
services:
template:
autowiring_type: 'Symfony\Component\Templating\EngineInterface'
alias: 'templating.engine.delegating'
But I still get the same error - application can not find which service to use for the EngineInterface
So the question is it possible to wire an interface to an already defined kernel service?
I've managed to handle similar problem when have to wire an abstract class to a service.
A service:
use Doctrine\Common\Cache\CacheProvider;
class MyOtherService
{
/** #var CacheProvider */
private $cache;
public function __construct(CacheProvider $cache)
{
$this->cache = $cache;
}
}
services:
memcache_cache:
class: 'Doctrine\Common\Cache\MemcacheCache'
autowiring_types: 'Doctrine\Common\Cache\CacheProvider'
calls:
- [ setMemcache, ["#memcache"] ]
- [ setNamespace, ["%memcache.prefix%"] ]
So, I've created a service (memcache_cache) and defined a autowiring_types for it. Every time the abstract CacheProvider is injected the memcache_cache should be used.
But it was easy as this is not a kernel service. I've defined it myself in a services.yml, whereas I don't have an access to extends the templating.engine.delegating service in a way to add the autowiring_types: 'Symfony\Component\Templating\EngineInterface'

Symfony autowiring monolog channels

Following this documentation, I can create many channels which will create services with the following name monolog.logger.<channel_name>
How can I inject these services into my service with DI injection and autowiring ?
class FooService
{
public function __construct(LoggerInterface $loggerInterface) { }
}
Yaml
#existing
foo_service:
class: AppBundle\Services\FooService
arguments: ["#monolog.logger.barchannel"]
# what I want to do
foo_service:
autowire: true # how to inject #monolog.logger.barchannel ?
Starting from MonologBundle 3.5 you can autowire different Monolog
channels by type-hinting your service arguments with the following
syntax: Psr\Log\LoggerInterface $<channel>Logger. For example, to
inject the service related to the app logger channel use this:
public function __construct(LoggerInterface $appLogger)
{
$this->logger = $appLogger;
}
https://symfony.com/doc/current/logging/channels_handlers.html#monolog-autowire-channels
I wrote (maybe more complicated) method. I don't want to tag my autowired services to tell symfony which channel to use.
Using symfony 4 with php 7.1.
I built LoggerFactory with all additional channels defined in monolog.channels.
My factory is in bundle, so in Bundle.php add
$container->addCompilerPass(
new LoggerFactoryPass(),
PassConfig::TYPE_BEFORE_OPTIMIZATION,
1
); // -1 call before monolog
This is important to call this compiler pass before monolog.bundle because monolog after pass removes parameters from container.
Now, LoggerFactoryPass
namespace Bundle\DependencyInjection\Compiler;
use Bundle\Service\LoggerFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class LoggerFactoryPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
* #param ContainerBuilder $container
* #throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* #throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
*/
public function process(ContainerBuilder $container): void
{
if (!$container->has(LoggerFactory::class) || !$container->hasDefinition('monolog.logger')) {
return;
}
$definition = $container->findDefinition(LoggerFactory::class);
foreach ($container->getParameter('monolog.additional_channels') as $channel) {
$loggerId = sprintf('monolog.logger.%s', $channel);
$definition->addMethodCall('addChannel', [
$channel,
new Reference($loggerId)
]);
}
}
}
and LoggerFactory
namespace Bundle\Service;
use Psr\Log\LoggerInterface;
class LoggerFactory
{
protected $channels = [];
public function addChannel($name, $loggerObject): void
{
$this->channels[$name] = $loggerObject;
}
/**
* #param string $channel
* #return LoggerInterface
* #throws \InvalidArgumentException
*/
public function getLogger(string $channel): LoggerInterface
{
if (!array_key_exists($channel, $this->channels)) {
throw new \InvalidArgumentException('You are trying to reach not defined logger channel');
}
return $this->channels[$channel];
}
}
So, now you can inject LoggerFactory, and choose your channel
public function acmeAction(LoggerFactory $factory)
{
$logger = $factory->getLogger('my_channel');
$logger->log('this is awesome!');
}
After some searching I have found some kind of workaround using tags and manually injecting several parameters to autowired service.
My answer looks similar to #Thomas-Landauer. The difference is, I do not have to manually create logger service, as the compiler pass from monolog bundle does this for me.
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '#logger'
tags:
- { name: monolog.logger, channel: barchannel }
You can use the bind parameter:
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: true
bind:
$loggerMyApi: '#monolog.logger.my_api'
Then you can use it in your service's constructor:
use Psr\Log\LoggerInterface;
...
public function __construct(LoggerInterface $loggerMyApi)
{
...
}
I didn't find a way to autowire the very logger channel. However, I found a way to use autowire in principle, and inject just the logger manually. With your class FooService, this is how services.yml could look like (Symfony 3.3):
# services.yml
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '#monolog.logger.barchannel'
So the "trick" is to inject the logger channel explicitly, while still having all other dependencies of this service injected through autowiring.
From the documentation it is now possible to autowire based on the type hinting of the argument name.
// autowires monolog with "foo" channel
public function __construct(\Psr\Log\LoggerInterface $fooLogger);
Essentially, you've got two options:
First, service tagging:
services:
App\Log\FooLogger:
arguments: ['#logger']
tags:
- { name: monolog.logger, channel: foo }
Then you can use your CustomLogger as a dependency elsewhere
Second, you can rely on Monolog to auto-register loggers for each custom channel within the configuration:
# config/packages/prod/monolog.yaml
monolog:
channels: ['foo', 'bar']
You will then have these services available: monolog.logger.foo, 'monolog.logger.bar'
You can then retrieve them from the service container, or wire them in manually, e.g:
services:
App\Lib\MyService:
$fooLogger: ['#monolog.logger.foo']
You can read more here and here.
Recently I was implement single point access to the all registered loggers by MonologBundle.
And also I tried to do some better solution - and did auto-generated logger decorators. Each class decorates one object of one of the registered monolog channel.
Link to the bundle adrenalinkin/monolog-autowire-bundle
For those still struggling with this one.
In Symfony 4.3, I had, on top of that, add an alias for the specific channel, because without that, it was working only on the dev environment : when building, the Unit Tests were all failing because the custom logger was an undefined service.
monolog.logger.my_custom_logger:
alias: Psr\Log\LoggerInterface
public: true
App\Logger\MyLogger:
arguments:
$logger: '#monolog.logger.my_custom_logger'

Resources