odan / slim4-skeleton

A Slim 4 Skeleton
https://odan.github.io/slim4-skeleton/
MIT License
439 stars 80 forks source link

Problem getting JWT to work as per your eBook #84

Closed noromamai closed 2 years ago

noromamai commented 2 years ago

Hi,

I set up the slim 4 framework following your ebook, but then ran into some issues with composer. After that I just took your slmi4-framwork from github and got it up and running. After that I followed the JWT part in your ebook but can't get it to work. I end up with a "Uncaught DI\DependencyException: Circular dependency detected while trying to resolve entry 'Slim\App'" error. Any help getting this solved would be greatly appreciated.

The error as i get it in postman when testing:

<br />
<b>Fatal error</b>:  Uncaught DI\DependencyException: Circular dependency detected while trying to resolve entry 'Slim\App' in /<project>/vendor/php-di/php-di/src/Container.php:384
Stack trace:
#0 /<project>/vendor/php-di/php-di/src/Container.php(139): DI\Container-&gt;resolveDefinition()
#1 /<project>/config/container.php(149): DI\Container-&gt;get()
#2 [internal function]: DI\Definition\Source\DefinitionFile-&gt;{closure}()
#3 /<project>/vendor/php-di/invoker/src/Invoker.php(74): call_user_func_array()
#4 /<project>/vendor/php-di/php-di/src/Definition/Resolver/FactoryResolver.php(80): Invoker\Invoker-&gt;call()
#5 /<project>/vendor/php-di/php-di/src/Definition/Resolver/ResolverDispatcher.php(71): DI\Definition\Resolver\FactoryResolver-&gt;resolve()
#6 /<project>/vendor/php-di/php-di/src/Container.php(390): DI\Definition\Resolver\ResolverDispatcher-&gt;resolve()
#7 /<project>/vendor/php-di/php-di/src/Container.php(139): DI\Container-&gt;resolveDefinition()
#8 /<project>/vendor/slim/slim/Slim/Factory/AppFactory.php(109): DI\Container-&gt;get()
#9 /<project>/config/container.php(37): Slim\Factory\AppFactory::createFromContainer()
#10 [internal function]: DI\Definition\Source\DefinitionFile-&gt;{closure}()
#11 /<project>/vendor/php-di/invoker/src/Invoker.php(74): call_user_func_array()
#12 /<project>/vendor/php-di/php-di/src/Definition/Resolver/FactoryResolver.php(80): Invoker\Invoker-&gt;call()
#13 /<project>/vendor/php-di/php-di/src/Definition/Resolver/ResolverDispatcher.php(71): DI\Definition\Resolver\FactoryResolver-&gt;resolve()
#14 /<project>/vendor/php-di/php-di/src/Container.php(390): DI\Definition\Resolver\ResolverDispatcher-&gt;resolve()
#15 /<project>/vendor/php-di/php-di/src/Container.php(139): DI\Container-&gt;resolveDefinition()
#16 /<project>/config/bootstrap.php(12): DI\Container-&gt;get()
#17 /<project>/public/index.php(6): require('...')
#18 {main}
  thrown in
<b>/<project>/vendor/php-di/php-di/src/Container.php</b> on line
<b>384</b>
<br />

contents of config/container.php:

<?php

use App\Routing\JwtAuth;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use App\Factory\LoggerFactory;
use App\Handler\DefaultErrorHandler;
use Cake\Database\Connection;
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;
use Selective\BasePath\BasePathMiddleware;
use Selective\Validation\Encoder\JsonEncoder;
use Selective\Validation\Middleware\ValidationExceptionMiddleware;
use Selective\Validation\Transformer\ErrorDetailsResultTransformer;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Interfaces\RouteParserInterface;
use Slim\Middleware\ErrorMiddleware;
use Slim\Views\PhpRenderer;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputOption;
use Tuupola\Middleware\HttpBasicAuthentication;

return [
    // Application settings
    'settings' => function () {
        return require __DIR__ . '/settings.php';
    },

    App::class => function (ContainerInterface $container) {
        $app = AppFactory::createFromContainer($container);

        // Register routes
        (require __DIR__ . '/routes.php')($app);

        // Register middleware
        (require __DIR__ . '/middleware.php')($app);

        return $app;
    },

    // HTTP factories
    ResponseFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(Psr17Factory::class);
    },

    ServerRequestFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(Psr17Factory::class);
    },

    StreamFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(Psr17Factory::class);
    },

    UploadedFileFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(Psr17Factory::class);
    },

    UriFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(Psr17Factory::class);
    },

    // The Slim RouterParser
    RouteParserInterface::class => function (ContainerInterface $container) {
        return $container->get(App::class)->getRouteCollector()->getRouteParser();
    },

    // The logger factory
    LoggerFactory::class => function (ContainerInterface $container) {
        return new LoggerFactory($container->get('settings')['logger']);
    },

    BasePathMiddleware::class => function (ContainerInterface $container) {
        return new BasePathMiddleware($container->get(App::class));
    },

    // Database connection
    Connection::class => function (ContainerInterface $container) {
        return new Connection($container->get('settings')['db']);
    },

    PDO::class => function (ContainerInterface $container) {
        $db = $container->get(Connection::class);
        $driver = $db->getDriver();
        $driver->connect();

        return $driver->getConnection();
    },

    ValidationExceptionMiddleware::class => function (ContainerInterface $container) {
        return new ValidationExceptionMiddleware(
            $container->get(ResponseFactoryInterface::class),
            new ErrorDetailsResultTransformer(),
            new JsonEncoder()
        );
    },

    ErrorMiddleware::class => function (ContainerInterface $container) {
        $settings = $container->get('settings')['error'];
        $app = $container->get(App::class);

        $logger = $container->get(LoggerFactory::class)
            ->addFileHandler('error.log')
            ->createLogger();

        $errorMiddleware = new ErrorMiddleware(
            $app->getCallableResolver(),
            $app->getResponseFactory(),
            (bool)$settings['display_error_details'],
            (bool)$settings['log_errors'],
            (bool)$settings['log_error_details'],
            $logger
        );

        $errorMiddleware->setDefaultErrorHandler($container->get(DefaultErrorHandler::class));

        return $errorMiddleware;
    },

    Application::class => function (ContainerInterface $container) {
        $application = new Application();

        $application->getDefinition()->addOption(
            new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'development')
        );

        foreach ($container->get('settings')['commands'] as $class) {
            $application->add($container->get($class));
        }

        return $application;
    },

    PhpRenderer::class => function (ContainerInterface $container) {
        return new PhpRenderer($container->get('settings')['template']);
    },

    HttpBasicAuthentication::class => function (ContainerInterface $container) {
        return new HttpBasicAuthentication($container->get('settings')['api_auth']);
    },

    ResponseFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(App::class)->getResponseFactory();
    },

    JwtAuth::class => function (ContainerInterface $container) {
        $configuration = $container->get(Configuration::class);
        $jwtSettings = $container->get('settings')['jwt'];
        $issuer = (string)$jwtSettings['issuer'];
        $lifetime = (int)$jwtSettings['lifetime'];

        return new JwtAuth($configuration, $issuer, $lifetime);
    },

    Configuration::class => function (ContainerInterface $container) {
        $jwtSettings = $container->get('settings')['jwt'];

        $privateKey = (string)$jwtSettings['private_key'];
        $publicKey = (string)$jwtSettings['public_key'];

        // Asymmetric algorithms use a private key for signature creation
        // and a public key for verification
        return Configuration::forAsymmetricSigner(
            new Sha256(),
            InMemory::plainText($privateKey),
            InMemory::plainText($publicKey)
        );
    },

];
odan commented 2 years ago

The DI container definitions are looking good. I guess there are something in the bootstrap that should be checked. Can you show your bootstrap.php file? Also search for other parts where you try to create a Slim App instance.

noromamai commented 2 years ago

this is the contents of bootstrap.php

<?php

use App\Factory\ContainerFactory;
use Slim\App;

require_once __DIR__ . '/../vendor/autoload.php';

// Build DI Container instance
$container = (new ContainerFactory())->createInstance();

// Create App instance
return $container->get(App::class);
odan commented 2 years ago

Ok, now I see it. There are two DI definitions for ResponseFactoryInterface::class.

Please try to remove this DI definition. This causes the circular dependency failure because the Slim AppFactory tries to fetch the ResponseFactory before the App instance exists.

  ResponseFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(App::class)->getResponseFactory();
    },
noromamai commented 2 years ago

Thanks for the help so far.

after commenting out that DI definition I no longer get that error. When using an incorrect username/password combination i get the expected 401 Unauthorized. But (still using your example code) when i use "user"/"secret" as in your example, i get an 500 error and nothing in the logs.

It seems to go wrong here in src/Routing/JwtAuth.php:

        // Builds a new token using the private key
        return $builder->getToken(
            $this->configuration->signer(),
            $this->configuration->signingKey()
        )->toString();

if i echo something before i can still see it in Postman, if i echo something afterwards i get nothing.

odan commented 2 years ago

To display more details, you can try to enable the error details and logging in the settings. Also, Xdebug is a useful helper.

$settings['error']['display_error_details'] = true;
$settings['error']['log_errors'] = true;

error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
noromamai commented 2 years ago

working! it was a stupid permission issue, very sorry about that. after turning on logging (php error logging was already turned on) i got an error about not being able to writ eto the error log. been working on two things at the same time and though i had already chown'ed it, but as it turns out it wasn't. sorry for bothering due to such a stupid mistake, but am very grateful and appreciate the great support.