welcome back to dyb-tech
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Command;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\KeyLoaderInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
#[AsCommand(name: 'lexik:jwt:check-config', description: 'Checks that the bundle is properly configured.')]
|
||||
class CheckConfigCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'lexik:jwt:check-config';
|
||||
|
||||
private $keyLoader;
|
||||
|
||||
private $signatureAlgorithm;
|
||||
|
||||
public function __construct(KeyLoaderInterface $keyLoader, $signatureAlgorithm)
|
||||
{
|
||||
$this->keyLoader = $keyLoader;
|
||||
$this->signatureAlgorithm = $signatureAlgorithm;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName(static::$defaultName)
|
||||
->setDescription('Checks JWT configuration');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
try {
|
||||
$this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PRIVATE);
|
||||
// No public key for HMAC
|
||||
if (false === strpos($this->signatureAlgorithm, 'HS')) {
|
||||
$this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PUBLIC);
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
$output->writeln('<error>' . $e->getMessage() . '</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$output->writeln('<info>The configuration seems correct.</info>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Command;
|
||||
|
||||
use Jose\Bundle\JoseFramework\JoseFrameworkBundle;
|
||||
use Jose\Component\Checker\ClaimCheckerManager;
|
||||
use Jose\Component\Core\Algorithm;
|
||||
use Jose\Component\Core\AlgorithmManagerFactory;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Jose\Component\Encryption\Algorithm\ContentEncryptionAlgorithm;
|
||||
use Jose\Component\Encryption\Algorithm\KeyEncryptionAlgorithm;
|
||||
use Jose\Component\Encryption\JWEBuilder;
|
||||
use Jose\Component\Encryption\JWELoader;
|
||||
use Jose\Component\KeyManagement\JWKFactory;
|
||||
use Jose\Component\Signature\JWSBuilder;
|
||||
use Jose\Component\Signature\JWSLoader;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\KeyLoaderInterface;
|
||||
use ParagonIE\ConstantTime\Base64UrlSafe;
|
||||
use Symfony\Bundle\FrameworkBundle\Command\AbstractConfigCommand;
|
||||
use Symfony\Component\Config\Definition\Processor;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
|
||||
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* @author Florent Morselli <florent.morselli@spomky-labs.com>
|
||||
*/
|
||||
#[AsCommand(name: 'lexik:jwt:enable-encryption', description: 'Enable Web-Token encryption support.')]
|
||||
final class EnableEncryptionConfigCommand extends AbstractConfigCommand
|
||||
{
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'lexik:jwt:enable-encryption';
|
||||
|
||||
/**
|
||||
* @var ?AlgorithmManagerFactory
|
||||
*/
|
||||
private $algorithmManagerFactory;
|
||||
|
||||
public function __construct(
|
||||
?AlgorithmManagerFactory $algorithmManagerFactory = null
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->algorithmManagerFactory = $algorithmManagerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName(static::$defaultName)
|
||||
->setDescription('Enable Web-Token encryption support.')
|
||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force the modification of the configuration, even if already set.')
|
||||
;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->algorithmManagerFactory !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$force = $input->getOption('force');
|
||||
$this->checkRequirements();
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('Web-Token Encryption support');
|
||||
$io->info('This tool will help you enabling the encryption support for Web-Token');
|
||||
|
||||
$algorithms = $this->algorithmManagerFactory->all();
|
||||
$availableKeyEncryptionAlgorithms = array_map(
|
||||
static function (Algorithm $algorithm): string {
|
||||
return $algorithm->name();
|
||||
},
|
||||
array_filter($algorithms, static function (Algorithm $algorithm): bool {
|
||||
return ($algorithm instanceof KeyEncryptionAlgorithm && $algorithm->name() !== 'dir');
|
||||
})
|
||||
);
|
||||
$availableContentEncryptionAlgorithms = array_map(
|
||||
static function (Algorithm $algorithm): string {
|
||||
return $algorithm->name();
|
||||
},
|
||||
array_filter($algorithms, static function (Algorithm $algorithm): bool {
|
||||
return $algorithm instanceof ContentEncryptionAlgorithm;
|
||||
})
|
||||
);
|
||||
|
||||
$keyEncryptionAlgorithmAlias = $io->choice('Key Encryption Algorithm', $availableKeyEncryptionAlgorithms);
|
||||
$contentEncryptionAlgorithmAlias = $io->choice('Content Encryption Algorithm', $availableContentEncryptionAlgorithms);
|
||||
$keyEncryptionAlgorithm = $algorithms[$keyEncryptionAlgorithmAlias];
|
||||
$contentEncryptionAlgorithm = $algorithms[$contentEncryptionAlgorithmAlias];
|
||||
|
||||
$continueOnDecryptionFailure = 'yes' === $io->choice('Continue decryption on failure', ['yes', 'no'], 'no');
|
||||
|
||||
$extension = $this->findExtension('lexik_jwt_authentication');
|
||||
$config = $this->getConfiguration($extension);
|
||||
if (!isset($config['encoder']['service']) || $config['encoder']['service'] !== 'lexik_jwt_authentication.encoder.web_token') {
|
||||
$io->error('Please migrate to WebToken first.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
if (!$force && ($config['access_token_issuance']['encryption']['enabled'] || $config['access_token_verification']['encryption']['enabled'])) {
|
||||
$io->error('Encryption support is already enabled.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$key = $this->generatePrivateKey($keyEncryptionAlgorithm);
|
||||
$keyset = $this->generatePublicKeyset($key, $keyEncryptionAlgorithm->name());
|
||||
|
||||
$config['access_token_issuance']['encryption'] = [
|
||||
'enabled' => true,
|
||||
'key_encryption_algorithm' => $keyEncryptionAlgorithm->name(),
|
||||
'content_encryption_algorithm' => $contentEncryptionAlgorithm->name(),
|
||||
'key' => json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
$config['access_token_verification']['encryption'] = [
|
||||
'enabled' => true,
|
||||
'continue_on_decryption_failure' => $continueOnDecryptionFailure,
|
||||
'header_checkers' => ['iat_with_clock_skew', 'nbf_with_clock_skew', 'exp_with_clock_skew'],
|
||||
'allowed_key_encryption_algorithms' => [$keyEncryptionAlgorithm->name()],
|
||||
'allowed_content_encryption_algorithms' => [$contentEncryptionAlgorithm->name()],
|
||||
'keyset' => json_encode($keyset, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
|
||||
$io->comment('Please replace the current configuration with the following parameters.');
|
||||
$io->section('# config/packages/lexik_jwt_authentication.yaml');
|
||||
$io->writeln(Yaml::dump([$extension->getAlias() => $config], 10));
|
||||
$io->section('# End of file');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function generatePublicKeyset(JWK $key, string $algorithm): JWKSet
|
||||
{
|
||||
$keyset = new JWKSet([$key->toPublic()]);
|
||||
switch ($key->get('kty')) {
|
||||
case 'oct':
|
||||
return $this->withOctKeys($keyset, $algorithm);
|
||||
case 'OKP':
|
||||
return $this->withOkpKeys($keyset, $algorithm, $key->get('crv'));
|
||||
case 'EC':
|
||||
return $this->withEcKeys($keyset, $algorithm, $key->get('crv'));
|
||||
case 'RSA':
|
||||
return $this->withRsaKeys($keyset, $algorithm);
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unsupported key type.');
|
||||
}
|
||||
}
|
||||
|
||||
private function withOctKeys(JWKSet $keyset, string $algorithm): JWKSet
|
||||
{
|
||||
$size = $this->getKeySize($algorithm);
|
||||
|
||||
return $keyset
|
||||
->with($this->createOctKey($size, $algorithm)->toPublic())
|
||||
->with($this->createOctKey($size, $algorithm)->toPublic())
|
||||
;
|
||||
}
|
||||
|
||||
private function withRsaKeys(JWKSet $keyset, string $algorithm): JWKSet
|
||||
{
|
||||
return $keyset
|
||||
->with($this->createRsaKey(2048, $algorithm)->toPublic())
|
||||
->with($this->createRsaKey(2048, $algorithm)->toPublic())
|
||||
;
|
||||
}
|
||||
|
||||
private function withOkpKeys(JWKSet $keyset, string $algorithm, string $curve): JWKSet
|
||||
{
|
||||
return $keyset
|
||||
->with($this->createOkpKey($curve, $algorithm)->toPublic())
|
||||
->with($this->createOkpKey($curve, $algorithm)->toPublic())
|
||||
;
|
||||
}
|
||||
|
||||
private function withEcKeys(JWKSet $keyset, string $algorithm, string $curve): JWKSet
|
||||
{
|
||||
return $keyset
|
||||
->with($this->createEcKey($curve, $algorithm)->toPublic())
|
||||
->with($this->createEcKey($curve, $algorithm)->toPublic())
|
||||
;
|
||||
}
|
||||
|
||||
private function generatePrivateKey(KeyEncryptionAlgorithm $algorithm): JWK
|
||||
{
|
||||
$keyType = current($algorithm->allowedKeyTypes());
|
||||
switch ($keyType) {
|
||||
case 'oct':
|
||||
return $this->createOctKey($this->getKeySize($algorithm->name()), $algorithm->name());
|
||||
case 'OKP':
|
||||
return $this->createOkpKey('X25519', $algorithm->name());
|
||||
case 'EC':
|
||||
return $this->createEcKey('P-256', $algorithm->name());
|
||||
case 'RSA':
|
||||
return $this->createRsaKey($this->getKeySize($algorithm->name()), $algorithm->name());
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unsupported key type.');
|
||||
}
|
||||
}
|
||||
|
||||
private function checkRequirements(): void
|
||||
{
|
||||
$requirements = [
|
||||
JoseFrameworkBundle::class => 'web-token/jwt-bundle',
|
||||
JWKFactory::class => 'web-token/jwt-key-mgmt',
|
||||
ClaimCheckerManager::class => 'web-token/jwt-checker',
|
||||
JWEBuilder::class => 'web-token/jwt-encryption',
|
||||
];
|
||||
if ($this->algorithmManagerFactory === null) {
|
||||
throw new \RuntimeException('The package "web-token/jwt-bundle" is missing. Please install it for using this migration tool.');
|
||||
}
|
||||
foreach (array_keys($requirements) as $requirement) {
|
||||
if (!class_exists($requirement)) {
|
||||
throw new \RuntimeException(sprintf('The package "%s" is missing. Please install it for using this migration tool.', $requirement));
|
||||
}
|
||||
}
|
||||
}
|
||||
private function getConfiguration(ExtensionInterface $extension): array
|
||||
{
|
||||
$container = $this->compileContainer();
|
||||
|
||||
$config = $this->getConfig($extension, $container);
|
||||
$uselessParameters = ['secret_key', 'public_key', 'pass_phrase', 'private_key_path', 'public_key_path', 'additional_public_keys'];
|
||||
foreach ($uselessParameters as $parameter) {
|
||||
unset($config[$parameter]);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function createOctKey(int $size, string $algorithm): JWK
|
||||
{
|
||||
return JWKFactory::createOctKey($size, $this->getOptions($algorithm));
|
||||
}
|
||||
|
||||
private function createRsaKey(int $size, string $algorithm): JWK
|
||||
{
|
||||
return JWKFactory::createRSAKey($size, $this->getOptions($algorithm));
|
||||
}
|
||||
|
||||
private function createOkpKey(string $curve, string $algorithm): JWK
|
||||
{
|
||||
return JWKFactory::createOKPKey($curve, $this->getOptions($algorithm));
|
||||
}
|
||||
|
||||
private function createEcKey(string $curve, string $algorithm): JWK
|
||||
{
|
||||
return JWKFactory::createECKey($curve, $this->getOptions($algorithm));
|
||||
}
|
||||
|
||||
private function compileContainer(): ContainerBuilder
|
||||
{
|
||||
$kernel = clone $this->getApplication()->getKernel();
|
||||
$kernel->boot();
|
||||
|
||||
$method = new \ReflectionMethod($kernel, 'buildContainer');
|
||||
$container = $method->invoke($kernel);
|
||||
$container->getCompiler()->compile($container);
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
private function getConfig(ExtensionInterface $extension, ContainerBuilder $container)
|
||||
{
|
||||
return $container->resolveEnvPlaceholders(
|
||||
$container->getParameterBag()->resolveValue(
|
||||
$this->getConfigForExtension($extension, $container)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array
|
||||
{
|
||||
$extensionAlias = $extension->getAlias();
|
||||
|
||||
$extensionConfig = [];
|
||||
foreach ($container->getCompilerPassConfig()->getPasses() as $pass) {
|
||||
if ($pass instanceof ValidateEnvPlaceholdersPass) {
|
||||
$extensionConfig = $pass->getExtensionConfig();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($extensionConfig[$extensionAlias])) {
|
||||
return $extensionConfig[$extensionAlias];
|
||||
}
|
||||
|
||||
// Fall back to default config if the extension has one
|
||||
|
||||
if (!$extension instanceof ConfigurationExtensionInterface) {
|
||||
throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias));
|
||||
}
|
||||
|
||||
$configs = $container->getExtensionConfig($extensionAlias);
|
||||
$configuration = $extension->getConfiguration($configs, $container);
|
||||
$this->validateConfiguration($extension, $configuration);
|
||||
|
||||
return (new Processor())->processConfiguration($configuration, $configs);
|
||||
}
|
||||
|
||||
private function getKeySize(string $algorithm): int
|
||||
{
|
||||
switch ($algorithm) {
|
||||
case 'RSA1_5':
|
||||
case 'RSA-OAEP':
|
||||
case 'RSA-OAEP-256':
|
||||
return 4096;
|
||||
case 'A128KW':
|
||||
case 'A128GCMKW':
|
||||
case 'PBES2-HS256+A128KW':
|
||||
return 128;
|
||||
case 'A192KW':
|
||||
case 'A192GCMKW':
|
||||
case 'PBES2-HS384+A192KW':
|
||||
return 192;
|
||||
case 'A256KW':
|
||||
case 'A256GCMKW':
|
||||
case 'PBES2-HS512+A256KW':
|
||||
return 256;
|
||||
default:
|
||||
throw new \LogicException('Unsupported algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
private function getOptions(string $algorithm): array
|
||||
{
|
||||
return [
|
||||
'use' => 'enc',
|
||||
'alg' => $algorithm,
|
||||
'kid'=> Base64UrlSafe::encodeUnpadded(random_bytes(16))
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
/**
|
||||
* @author Beno!t POLASZEK <bpolaszek@gmail.com>
|
||||
*/
|
||||
#[AsCommand(name: 'lexik:jwt:generate-keypair', description: 'Generate public/private keys for use in your application.')]
|
||||
final class GenerateKeyPairCommand extends Command
|
||||
{
|
||||
private const ACCEPTED_ALGORITHMS = [
|
||||
'RS256',
|
||||
'RS384',
|
||||
'RS512',
|
||||
'HS256',
|
||||
'HS384',
|
||||
'HS512',
|
||||
'ES256',
|
||||
'ES384',
|
||||
'ES512',
|
||||
];
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'lexik:jwt:generate-keypair';
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $secretKey;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $publicKey;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $passphrase;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $algorithm;
|
||||
|
||||
public function __construct(Filesystem $filesystem, ?string $secretKey, ?string $publicKey, ?string $passphrase, string $algorithm)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->filesystem = $filesystem;
|
||||
$this->secretKey = $secretKey;
|
||||
$this->publicKey = $publicKey;
|
||||
$this->passphrase = $passphrase;
|
||||
$this->algorithm = $algorithm;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generate public/private keys for use in your application.');
|
||||
$this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not update key files.');
|
||||
$this->addOption('skip-if-exists', null, InputOption::VALUE_NONE, 'Do not update key files if they already exist.');
|
||||
$this->addOption('overwrite', null, InputOption::VALUE_NONE, 'Overwrite key files if they already exist.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
if (!in_array($this->algorithm, self::ACCEPTED_ALGORITHMS, true)) {
|
||||
$io->error(sprintf('Cannot generate key pair with the provided algorithm `%s`.', $this->algorithm));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
[$secretKey, $publicKey] = $this->generateKeyPair($this->passphrase);
|
||||
|
||||
if (true === $input->getOption('dry-run')) {
|
||||
$io->success('Your keys have been generated!');
|
||||
$io->newLine();
|
||||
$io->writeln(sprintf('Update your private key in <info>%s</info>:', $this->secretKey));
|
||||
$io->writeln($secretKey);
|
||||
$io->newLine();
|
||||
$io->writeln(sprintf('Update your public key in <info>%s</info>:', $this->publicKey));
|
||||
$io->writeln($publicKey);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$this->secretKey || !$this->publicKey) {
|
||||
throw new LogicException(sprintf('The "lexik_jwt_authentication.secret_key" and "lexik_jwt_authentication.public_key" config options must not be empty for using the "%s" command.', self::$defaultName));
|
||||
}
|
||||
|
||||
$alreadyExists = $this->filesystem->exists($this->secretKey) || $this->filesystem->exists($this->publicKey);
|
||||
|
||||
if ($alreadyExists) {
|
||||
try {
|
||||
$this->handleExistingKeys($input);
|
||||
} catch (\RuntimeException $e) {
|
||||
if (0 === $e->getCode()) {
|
||||
$io->comment($e->getMessage());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$io->error($e->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!$io->confirm('You are about to replace your existing keys. Are you sure you wish to continue?')) {
|
||||
$io->comment('Your action was canceled.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$this->filesystem->dumpFile($this->secretKey, $secretKey);
|
||||
$this->filesystem->dumpFile($this->publicKey, $publicKey);
|
||||
|
||||
$io->success('Done!');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function handleExistingKeys(InputInterface $input): void
|
||||
{
|
||||
if (true === $input->getOption('skip-if-exists') && true === $input->getOption('overwrite')) {
|
||||
throw new \RuntimeException('Both options `--skip-if-exists` and `--overwrite` cannot be combined.', 1);
|
||||
}
|
||||
|
||||
if (true === $input->getOption('skip-if-exists')) {
|
||||
throw new \RuntimeException('Your key files already exist, they won\'t be overriden.', 0);
|
||||
}
|
||||
|
||||
if (false === $input->getOption('overwrite')) {
|
||||
throw new \RuntimeException('Your keys already exist. Use the `--overwrite` option to force regeneration.', 1);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateKeyPair($passphrase): array
|
||||
{
|
||||
$config = $this->buildOpenSSLConfiguration();
|
||||
|
||||
$resource = \openssl_pkey_new($config);
|
||||
if (false === $resource) {
|
||||
throw new \RuntimeException(\openssl_error_string());
|
||||
}
|
||||
|
||||
$success = \openssl_pkey_export($resource, $privateKey, $passphrase);
|
||||
|
||||
if (false === $success) {
|
||||
throw new \RuntimeException(\openssl_error_string());
|
||||
}
|
||||
|
||||
$publicKeyData = \openssl_pkey_get_details($resource);
|
||||
|
||||
if (false === $publicKeyData) {
|
||||
throw new \RuntimeException(\openssl_error_string());
|
||||
}
|
||||
|
||||
$publicKey = $publicKeyData['key'];
|
||||
|
||||
return [$privateKey, $publicKey];
|
||||
}
|
||||
|
||||
private function buildOpenSSLConfiguration(): array
|
||||
{
|
||||
$digestAlgorithms = [
|
||||
'RS256' => 'sha256',
|
||||
'RS384' => 'sha384',
|
||||
'RS512' => 'sha512',
|
||||
'HS256' => 'sha256',
|
||||
'HS384' => 'sha384',
|
||||
'HS512' => 'sha512',
|
||||
'ES256' => 'sha256',
|
||||
'ES384' => 'sha384',
|
||||
'ES512' => 'sha512',
|
||||
];
|
||||
$privateKeyBits = [
|
||||
'RS256' => 2048,
|
||||
'RS384' => 2048,
|
||||
'RS512' => 4096,
|
||||
'HS256' => 512,
|
||||
'HS384' => 512,
|
||||
'HS512' => 512,
|
||||
'ES256' => 384,
|
||||
'ES384' => 512,
|
||||
'ES512' => 1024,
|
||||
];
|
||||
$privateKeyTypes = [
|
||||
'RS256' => \OPENSSL_KEYTYPE_RSA,
|
||||
'RS384' => \OPENSSL_KEYTYPE_RSA,
|
||||
'RS512' => \OPENSSL_KEYTYPE_RSA,
|
||||
'HS256' => \OPENSSL_KEYTYPE_DH,
|
||||
'HS384' => \OPENSSL_KEYTYPE_DH,
|
||||
'HS512' => \OPENSSL_KEYTYPE_DH,
|
||||
'ES256' => \OPENSSL_KEYTYPE_EC,
|
||||
'ES384' => \OPENSSL_KEYTYPE_EC,
|
||||
'ES512' => \OPENSSL_KEYTYPE_EC,
|
||||
];
|
||||
|
||||
$curves = [
|
||||
'ES256' => 'secp256k1',
|
||||
'ES384' => 'secp384r1',
|
||||
'ES512' => 'secp521r1',
|
||||
];
|
||||
|
||||
$config = [
|
||||
'digest_alg' => $digestAlgorithms[$this->algorithm],
|
||||
'private_key_type' => $privateKeyTypes[$this->algorithm],
|
||||
'private_key_bits' => $privateKeyBits[$this->algorithm],
|
||||
];
|
||||
|
||||
if (isset($curves[$this->algorithm])) {
|
||||
$config['curve_name'] = $curves[$this->algorithm];
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Command;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
/**
|
||||
* GenerateTokenCommand.
|
||||
*
|
||||
* @author Samuel Roze <samuel.roze@gmail.com>
|
||||
*/
|
||||
#[AsCommand(name: 'lexik:jwt:generate-token', description: 'Generates a JWT token for a given user.')]
|
||||
class GenerateTokenCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'lexik:jwt:generate-token';
|
||||
|
||||
private $tokenManager;
|
||||
|
||||
/** @var \Traversable|UserProviderInterface[] */
|
||||
private $userProviders;
|
||||
|
||||
public function __construct(JWTTokenManagerInterface $tokenManager, \Traversable $userProviders)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->tokenManager = $tokenManager;
|
||||
$this->userProviders = $userProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName(static::$defaultName)
|
||||
->setDescription('Generates a JWT token')
|
||||
->addArgument('username', InputArgument::REQUIRED, 'Username of user to be retreived from user provider')
|
||||
->addOption('ttl', 't', InputOption::VALUE_REQUIRED, 'Ttl in seconds to be added to current time. If not provided, the ttl configured in the bundle will be used. Use 0 to generate token without exp')
|
||||
->addOption('user-class', 'c', InputOption::VALUE_REQUIRED, 'Userclass is used to determine which user provider to use')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if ($this->userProviders instanceof \Countable && 0 === \count($this->userProviders)) {
|
||||
throw new \RuntimeException('You must have at least 1 configured user provider to generate a token.');
|
||||
}
|
||||
|
||||
if (!$userClass = $input->getOption('user-class')) {
|
||||
if (1 < \count($userProviders = iterator_to_array($this->userProviders))) {
|
||||
throw new \RuntimeException('The "--user-class" option must be passed as there is more than 1 configured user provider.');
|
||||
}
|
||||
|
||||
$userProvider = current($userProviders);
|
||||
} else {
|
||||
$userProvider = null;
|
||||
|
||||
foreach ($this->userProviders as $provider) {
|
||||
if ($provider->supportsClass($userClass)) {
|
||||
$userProvider = $provider;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$userProvider) {
|
||||
throw new \RuntimeException(sprintf('There is no configured user provider for class "%s".', $userClass));
|
||||
}
|
||||
}
|
||||
|
||||
if (method_exists($userProvider, 'loadUserByIdentifier')) {
|
||||
$user = $userProvider->loadUserByIdentifier($input->getArgument('username'));
|
||||
} else {
|
||||
$user = $userProvider->loadUserByUsername($input->getArgument('username'));
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
|
||||
if (null !== $input->getOption('ttl') && ((int) $input->getOption('ttl')) == 0) {
|
||||
$payload['exp'] = 0;
|
||||
} elseif (null !== $input->getOption('ttl') && ((int) $input->getOption('ttl')) > 0) {
|
||||
$payload['exp'] = time() + $input->getOption('ttl');
|
||||
}
|
||||
|
||||
$token = $this->tokenManager->createFromPayload($user, $payload);
|
||||
|
||||
$output->writeln([
|
||||
'',
|
||||
'<info>' . $token . '</info>',
|
||||
'',
|
||||
]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Command;
|
||||
|
||||
use Jose\Bundle\JoseFramework\JoseFrameworkBundle;
|
||||
use Jose\Component\Checker\ClaimCheckerManager;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Jose\Component\KeyManagement\JWKFactory;
|
||||
use Jose\Component\Signature\JWSBuilder;
|
||||
use Jose\Component\Signature\JWSLoader;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\KeyLoaderInterface;
|
||||
use ParagonIE\ConstantTime\Base64UrlSafe;
|
||||
use Symfony\Bundle\FrameworkBundle\Command\AbstractConfigCommand;
|
||||
use Symfony\Component\Config\Definition\Processor;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
|
||||
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* @author Florent Morselli <florent.morselli@spomky-labs.com>
|
||||
*/
|
||||
#[AsCommand(name: 'lexik:jwt:migrate-config', description: 'Migrate LexikJWTAuthenticationBundle configuration to the Web-Token one.')]
|
||||
final class MigrateConfigCommand extends AbstractConfigCommand
|
||||
{
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'lexik:jwt:migrate-config';
|
||||
|
||||
/**
|
||||
* @var KeyLoaderInterface
|
||||
*/
|
||||
private $keyLoader;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $signatureAlgorithm;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $passphrase;
|
||||
|
||||
public function __construct(
|
||||
KeyLoaderInterface $keyLoader,
|
||||
string $passphrase,
|
||||
string $signatureAlgorithm
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->keyLoader = $keyLoader;
|
||||
$this->passphrase = $passphrase === '' ? null : $passphrase;
|
||||
$this->signatureAlgorithm = $signatureAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName(static::$defaultName)
|
||||
->setDescription('Migrate the configuration to Web-Token')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->checkRequirements();
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('Web-Token Migration tool');
|
||||
$io->info('This tool will help you converting the current LexikJWTAuthenticationBundle configuration to support Web-Token');
|
||||
|
||||
try {
|
||||
$key = $this->getKey();
|
||||
$keyset = $this->getKeyset($key, $this->signatureAlgorithm);
|
||||
} catch (\RuntimeException $e) {
|
||||
$io->error('An error occurred: ' . $e->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$extension = $this->findExtension('lexik_jwt_authentication');
|
||||
$config = $this->getConfiguration($extension);
|
||||
|
||||
foreach ($config['set_cookies'] as $cookieConfig) {
|
||||
if ($cookieConfig['split'] !== []) {
|
||||
$io->error('Web-Token is not compatible with the cookie split feature. Please disable this option before using this migration tool.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$config['encoder'] = ['service' => 'lexik_jwt_authentication.encoder.web_token'];
|
||||
$config['access_token_issuance'] = [
|
||||
'enabled' => true,
|
||||
'signature' => [
|
||||
'signature_algorithm' => $this->signatureAlgorithm,
|
||||
'key' => json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
||||
]
|
||||
];
|
||||
$config['access_token_verification'] = [
|
||||
'enabled' => true,
|
||||
'signature' => [
|
||||
'allowed_signature_algorithms' => [$this->signatureAlgorithm],
|
||||
'keyset' => json_encode($keyset, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
||||
]
|
||||
];
|
||||
|
||||
$io->comment('Please replace the current configuration with the following parameters.');
|
||||
$io->section('# config/packages/lexik_jwt_authentication.yaml');
|
||||
$io->writeln(Yaml::dump([$extension->getAlias() => $config], 10));
|
||||
$io->section('# End of file');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function getKeyset(JWK $key, string $algorithm): JWKSet
|
||||
{
|
||||
$keyset = new JWKSet([$key->toPublic()]);
|
||||
switch ($key->get('kty')) {
|
||||
case 'oct':
|
||||
return $this->withOctKeys($keyset, $algorithm);
|
||||
case 'OKP':
|
||||
return $this->withOkpKeys($keyset, $algorithm, $key->get('crv'));
|
||||
case 'EC':
|
||||
return $this->withEcKeys($keyset, $algorithm, $key->get('crv'));
|
||||
case 'RSA':
|
||||
return $this->withRsaKeys($keyset, $algorithm);
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unsupported key type.');
|
||||
}
|
||||
}
|
||||
|
||||
private function withOctKeys(JWKSet $keyset, string $algorithm): JWKSet
|
||||
{
|
||||
$size = $this->getKeySize($algorithm);
|
||||
|
||||
return $keyset
|
||||
->with($this->createOctKey($size, $algorithm)->toPublic())
|
||||
->with($this->createOctKey($size, $algorithm)->toPublic())
|
||||
;
|
||||
}
|
||||
|
||||
private function withRsaKeys(JWKSet $keyset, string $algorithm): JWKSet
|
||||
{
|
||||
return $keyset
|
||||
->with($this->createRsaKey(2048, $algorithm)->toPublic())
|
||||
->with($this->createRsaKey(2048, $algorithm)->toPublic())
|
||||
;
|
||||
}
|
||||
|
||||
private function withOkpKeys(JWKSet $keyset, string $algorithm, string $curve): JWKSet
|
||||
{
|
||||
return $keyset
|
||||
->with($this->createOkpKey($curve, $algorithm)->toPublic())
|
||||
->with($this->createOkpKey($curve, $algorithm)->toPublic())
|
||||
;
|
||||
}
|
||||
|
||||
private function withEcKeys(JWKSet $keyset, string $algorithm, string $curve): JWKSet
|
||||
{
|
||||
return $keyset
|
||||
->with($this->createEcKey($curve, $algorithm)->toPublic())
|
||||
->with($this->createEcKey($curve, $algorithm)->toPublic())
|
||||
;
|
||||
}
|
||||
|
||||
private function getKey(): JWK
|
||||
{
|
||||
$additionalValues = [
|
||||
'use' => 'sig',
|
||||
'alg' => $this->signatureAlgorithm,
|
||||
];
|
||||
// No public key for HMAC
|
||||
if (false !== strpos($this->signatureAlgorithm, 'HS')) {
|
||||
return JWKFactory::createFromSecret(
|
||||
$this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PUBLIC),
|
||||
$additionalValues
|
||||
);
|
||||
}
|
||||
return JWKFactory::createFromKey(
|
||||
$this->keyLoader->loadKey(KeyLoaderInterface::TYPE_PRIVATE),
|
||||
$this->passphrase,
|
||||
$additionalValues
|
||||
);
|
||||
}
|
||||
|
||||
private function checkRequirements(): void
|
||||
{
|
||||
$requirements = [
|
||||
JoseFrameworkBundle::class => 'web-token/jwt-bundle',
|
||||
JWKFactory::class => 'web-token/jwt-key-mgmt',
|
||||
ClaimCheckerManager::class => 'web-token/jwt-checker',
|
||||
JWSBuilder::class => 'web-token/jwt-signature',
|
||||
];
|
||||
|
||||
foreach (array_keys($requirements) as $requirement) {
|
||||
if (!class_exists($requirement)) {
|
||||
throw new \RuntimeException(sprintf('The package "%s" is missing. Please install it for using this migration tool.', $requirement));
|
||||
}
|
||||
}
|
||||
}
|
||||
private function getConfiguration(ExtensionInterface $extension): array
|
||||
{
|
||||
$container = $this->compileContainer();
|
||||
|
||||
$config = $this->getConfig($extension, $container);
|
||||
$uselessParameters = ['secret_key', 'public_key', 'pass_phrase', 'private_key_path', 'public_key_path', 'additional_public_keys'];
|
||||
foreach ($uselessParameters as $parameter) {
|
||||
unset($config[$parameter]);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function createOctKey(int $size, string $algorithm): JWK
|
||||
{
|
||||
return JWKFactory::createOctKey($size, $this->getOptions($algorithm));
|
||||
}
|
||||
|
||||
private function createRsaKey(int $size, string $algorithm): JWK
|
||||
{
|
||||
return JWKFactory::createRSAKey($size, $this->getOptions($algorithm));
|
||||
}
|
||||
|
||||
private function createOkpKey(string $curve, string $algorithm): JWK
|
||||
{
|
||||
return JWKFactory::createOKPKey($curve, $this->getOptions($algorithm));
|
||||
}
|
||||
|
||||
private function createEcKey(string $curve, string $algorithm): JWK
|
||||
{
|
||||
return JWKFactory::createECKey($curve, $this->getOptions($algorithm));
|
||||
}
|
||||
|
||||
private function compileContainer(): ContainerBuilder
|
||||
{
|
||||
$kernel = clone $this->getApplication()->getKernel();
|
||||
$kernel->boot();
|
||||
|
||||
$method = new \ReflectionMethod($kernel, 'buildContainer');
|
||||
$container = $method->invoke($kernel);
|
||||
$container->getCompiler()->compile($container);
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
private function getConfig(ExtensionInterface $extension, ContainerBuilder $container)
|
||||
{
|
||||
return $container->resolveEnvPlaceholders(
|
||||
$container->getParameterBag()->resolveValue(
|
||||
$this->getConfigForExtension($extension, $container)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array
|
||||
{
|
||||
$extensionAlias = $extension->getAlias();
|
||||
|
||||
$extensionConfig = [];
|
||||
foreach ($container->getCompilerPassConfig()->getPasses() as $pass) {
|
||||
if ($pass instanceof ValidateEnvPlaceholdersPass) {
|
||||
$extensionConfig = $pass->getExtensionConfig();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($extensionConfig[$extensionAlias])) {
|
||||
return $extensionConfig[$extensionAlias];
|
||||
}
|
||||
|
||||
// Fall back to default config if the extension has one
|
||||
|
||||
if (!$extension instanceof ConfigurationExtensionInterface) {
|
||||
throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias));
|
||||
}
|
||||
|
||||
$configs = $container->getExtensionConfig($extensionAlias);
|
||||
$configuration = $extension->getConfiguration($configs, $container);
|
||||
$this->validateConfiguration($extension, $configuration);
|
||||
|
||||
return (new Processor())->processConfiguration($configuration, $configs);
|
||||
}
|
||||
|
||||
private function getKeySize(string $algorithm): int
|
||||
{
|
||||
switch ($algorithm) {
|
||||
case 'HS256':
|
||||
case 'HS256/64':
|
||||
return 256;
|
||||
case 'HS384':
|
||||
return 384;
|
||||
case 'HS512':
|
||||
return 512;
|
||||
default:
|
||||
dump($algorithm);
|
||||
throw new \LogicException('Unsupported algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
private function getOptions(string $algorithm): array
|
||||
{
|
||||
return [
|
||||
'use' => 'sig',
|
||||
'alg' => $algorithm,
|
||||
'kid'=> Base64UrlSafe::encodeUnpadded(random_bytes(16))
|
||||
];
|
||||
}
|
||||
}
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class ApiPlatformOpenApiPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition('lexik_jwt_authentication.api_platform.openapi.factory') || !$container->hasParameter('security.firewalls')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$checkPath = null;
|
||||
$usernamePath = null;
|
||||
$passwordPath = null;
|
||||
$firewalls = $container->getParameter('security.firewalls');
|
||||
foreach ($firewalls as $firewallName) {
|
||||
if ($container->hasDefinition('security.authenticator.json_login.' . $firewallName)) {
|
||||
$firewallOptions = $container->getDefinition('security.authenticator.json_login.' . $firewallName)->getArgument(4);
|
||||
$checkPath = $firewallOptions['check_path'];
|
||||
$usernamePath = $firewallOptions['username_path'];
|
||||
$passwordPath = $firewallOptions['password_path'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$openApiFactoryDefinition = $container->getDefinition('lexik_jwt_authentication.api_platform.openapi.factory');
|
||||
$checkPathArg = $openApiFactoryDefinition->getArgument(1);
|
||||
$usernamePathArg = $openApiFactoryDefinition->getArgument(2);
|
||||
$passwordPathArg = $openApiFactoryDefinition->getArgument(3);
|
||||
|
||||
if (!$checkPath && !$checkPathArg) {
|
||||
$container->removeDefinition('lexik_jwt_authentication.api_platform.openapi.factory');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$checkPathArg) {
|
||||
$openApiFactoryDefinition->replaceArgument(1, $checkPath);
|
||||
}
|
||||
if (!$usernamePathArg) {
|
||||
$openApiFactoryDefinition->replaceArgument(2, $usernamePath ?? 'username');
|
||||
}
|
||||
if (!$passwordPathArg) {
|
||||
$openApiFactoryDefinition->replaceArgument(3, $passwordPath ?? 'password');
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Guard\JWTTokenAuthenticator;
|
||||
use Symfony\Component\Config\Definition\BaseNode;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class DeprecateLegacyGuardAuthenticatorPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasParameter('lexik_jwt_authentication.authenticator_manager_enabled') || !$container->getParameter('lexik_jwt_authentication.authenticator_manager_enabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$deprecationArgs = ['The "%service_id%" service is deprecated and will be removed in 3.0, use the new "jwt" authenticator instead.'];
|
||||
if (method_exists(BaseNode::class, 'getDeprecation')) {
|
||||
$deprecationArgs = ['lexik/jwt-authentication-bundle', '2.7', 'The "%service_id%" service is deprecated and will be removed in 3.0, use the new "jwt" authenticator instead.'];
|
||||
}
|
||||
|
||||
$container
|
||||
->getDefinition('lexik_jwt_authentication.security.guard.jwt_token_authenticator')
|
||||
->setDeprecated(...$deprecationArgs);
|
||||
}
|
||||
}
|
||||
vendor/lexik/jwt-authentication-bundle/DependencyInjection/Compiler/WireGenerateTokenCommandPass.php
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class WireGenerateTokenCommandPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition('lexik_jwt_authentication.generate_token_command') || !$container->hasDefinition('security.context_listener')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$container
|
||||
->getDefinition('lexik_jwt_authentication.generate_token_command')
|
||||
->replaceArgument(1, $container->getDefinition('security.context_listener')->getArgument(1))
|
||||
;
|
||||
}
|
||||
}
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection;
|
||||
|
||||
use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
|
||||
use Symfony\Component\Config\Definition\BaseNode;
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* LexikJWTAuthenticationBundle Configuration.
|
||||
*/
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
public const INVALID_KEY_PATH = "The file %s doesn't exist or is not readable.\nIf the configured encoder doesn't need this to be configured, please don't set this option or leave it null.";
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$treeBuilder = new TreeBuilder('lexik_jwt_authentication');
|
||||
|
||||
$treeBuilder
|
||||
->getRootNode()
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('private_key_path')
|
||||
->setDeprecated(...$this->getDeprecationParameters('The "%path%.%node%" configuration key is deprecated since version 2.5. Use "%path%.secret_key" instead.', '2.5'))
|
||||
->defaultNull()
|
||||
->end()
|
||||
->scalarNode('public_key_path')
|
||||
->setDeprecated(...$this->getDeprecationParameters('The "%path%.%node%" configuration key is deprecated since version 2.5. Use "%path%.public_key" instead.', '2.5'))
|
||||
->defaultNull()
|
||||
->end()
|
||||
->scalarNode('public_key')
|
||||
->info('The key used to sign tokens (useless for HMAC). If not set, the key will be automatically computed from the secret key.')
|
||||
->defaultNull()
|
||||
->end()
|
||||
->arrayNode('additional_public_keys')
|
||||
->info('Multiple public keys to try to verify token signature. If none is given, it will use the key provided in "public_key".')
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->scalarNode('secret_key')
|
||||
->info('The key used to sign tokens. It can be a raw secret (for HMAC), a raw RSA/ECDSA key or the path to a file itself being plaintext or PEM.')
|
||||
->defaultNull()
|
||||
->end()
|
||||
->scalarNode('pass_phrase')
|
||||
->info('The key passphrase (useless for HMAC)')
|
||||
->defaultValue('')
|
||||
->end()
|
||||
->scalarNode('token_ttl')
|
||||
->defaultValue(3600)
|
||||
->end()
|
||||
->booleanNode('allow_no_expiration')
|
||||
->info('Allow tokens without "exp" claim (i.e. indefinitely valid, no lifetime) to be considered valid. Caution: usage of this should be rare.')
|
||||
->defaultFalse()
|
||||
->end()
|
||||
->scalarNode('clock_skew')
|
||||
->defaultValue(0)
|
||||
->end()
|
||||
->arrayNode('encoder')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('service')
|
||||
->defaultValue('lexik_jwt_authentication.encoder.lcobucci')
|
||||
->end()
|
||||
->scalarNode('signature_algorithm')
|
||||
->defaultValue('RS256')
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->enumNode('crypto_engine')
|
||||
->values(['openssl', 'phpseclib'])
|
||||
->defaultValue('openssl')
|
||||
->setDeprecated(...$this->getDeprecationParameters('The "%path%.%node%" configuration key is deprecated since version 2.5, built-in encoders support OpenSSL only', '2.5'))
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('user_identity_field')
|
||||
->setDeprecated(...$this->getDeprecationParameters('The "%path%.%node%" configuration key is deprecated since version 2.16, use "%path%.user_id_claim" or implement "' . UserInterface::class . '::getUserIdentifier()" instead.', '2.16'))
|
||||
->defaultValue('username')
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->scalarNode('user_id_claim')
|
||||
->defaultNull()
|
||||
->info('If null, the user ID claim will have the same name as the one defined by the option "user_identity_field"')
|
||||
->end()
|
||||
->append($this->getTokenExtractorsNode())
|
||||
->scalarNode('remove_token_from_body_when_cookies_used')
|
||||
->defaultTrue()
|
||||
->end()
|
||||
->arrayNode('set_cookies')
|
||||
->fixXmlConfig('set_cookie')
|
||||
->normalizeKeys(false)
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('lifetime')
|
||||
->defaultNull()
|
||||
->info('The cookie lifetime. If null, the "token_ttl" option value will be used')
|
||||
->end()
|
||||
->enumNode('samesite')
|
||||
->values([Cookie::SAMESITE_NONE, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT])
|
||||
->defaultValue(Cookie::SAMESITE_LAX)
|
||||
->end()
|
||||
->scalarNode('path')->defaultValue('/')->cannotBeEmpty()->end()
|
||||
->scalarNode('domain')->defaultNull()->end()
|
||||
->scalarNode('secure')->defaultTrue()->end()
|
||||
->scalarNode('httpOnly')->defaultTrue()->end()
|
||||
->scalarNode('partitioned')->defaultFalse()->end()
|
||||
->arrayNode('split')
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('api_platform')
|
||||
->canBeEnabled()
|
||||
->info('API Platform compatibility: add check_path in OpenAPI documentation.')
|
||||
->children()
|
||||
->scalarNode('check_path')
|
||||
->defaultNull()
|
||||
->info('The login check path to add in OpenAPI.')
|
||||
->end()
|
||||
->scalarNode('username_path')
|
||||
->defaultNull()
|
||||
->info('The path to the username in the JSON body.')
|
||||
->end()
|
||||
->scalarNode('password_path')
|
||||
->defaultNull()
|
||||
->info('The path to the password in the JSON body.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('access_token_issuance')
|
||||
->fixXmlConfig('access_token_issuance')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->arrayNode('signature')
|
||||
->fixXmlConfig('signature')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('algorithm')
|
||||
->isRequired()
|
||||
->info('The algorithm use to sign the access tokens.')
|
||||
->end()
|
||||
->scalarNode('key')
|
||||
->isRequired()
|
||||
->info('The signature key. It shall be JWK encoded.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('encryption')
|
||||
->fixXmlConfig('encryption')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->scalarNode('key_encryption_algorithm')
|
||||
->isRequired()
|
||||
->cannotBeEmpty()
|
||||
->info('The key encryption algorithm is used to encrypt the token.')
|
||||
->end()
|
||||
->scalarNode('content_encryption_algorithm')
|
||||
->isRequired()
|
||||
->cannotBeEmpty()
|
||||
->info('The key encryption algorithm is used to encrypt the token.')
|
||||
->end()
|
||||
->scalarNode('key')
|
||||
->isRequired()
|
||||
->info('The encryption key. It shall be JWK encoded.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('access_token_verification')
|
||||
->fixXmlConfig('access_token_verification')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->arrayNode('signature')
|
||||
->fixXmlConfig('signature')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->arrayNode('header_checkers')
|
||||
->fixXmlConfig('header_checkers')
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue([])
|
||||
->info('The headers to be checked for validating the JWS.')
|
||||
->end()
|
||||
->arrayNode('claim_checkers')
|
||||
->fixXmlConfig('claim_checkers')
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue(['exp_with_clock_skew', 'iat_with_clock_skew', 'nbf_with_clock_skew'])
|
||||
->info('The claims to be checked for validating the JWS.')
|
||||
->end()
|
||||
->arrayNode('mandatory_claims')
|
||||
->fixXmlConfig('mandatory_claims')
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue([])
|
||||
->info('The list of claims that shall be present in the JWS.')
|
||||
->end()
|
||||
->arrayNode('allowed_algorithms')
|
||||
->fixXmlConfig('allowed_algorithms')
|
||||
->scalarPrototype()->end()
|
||||
->requiresAtLeastOneElement()
|
||||
->info('The algorithms allowed to be used for token verification.')
|
||||
->end()
|
||||
->scalarNode('keyset')
|
||||
->isRequired()
|
||||
->info('The signature keyset. It shall be JWKSet encoded.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('encryption')
|
||||
->fixXmlConfig('encryption')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->booleanNode('continue_on_decryption_failure')
|
||||
->defaultFalse()
|
||||
->info('If enable, non-encrypted tokens or tokens that failed during decryption or verification processes are accepted.')
|
||||
->end()
|
||||
->arrayNode('header_checkers')
|
||||
->fixXmlConfig('header_checkers')
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue(['iat_with_clock_skew', 'nbf_with_clock_skew', 'exp_with_clock_skew'])
|
||||
->info('The headers to be checked for validating the JWE.')
|
||||
->end()
|
||||
->arrayNode('allowed_key_encryption_algorithms')
|
||||
->fixXmlConfig('allowed_key_encryption_algorithms')
|
||||
->scalarPrototype()->end()
|
||||
->requiresAtLeastOneElement()
|
||||
->info('The key encryption algorithm is used to encrypt the token.')
|
||||
->end()
|
||||
->arrayNode('allowed_content_encryption_algorithms')
|
||||
->fixXmlConfig('allowed_content_encryption_algorithms')
|
||||
->scalarPrototype()->end()
|
||||
->requiresAtLeastOneElement()
|
||||
->info('The key encryption algorithm is used to encrypt the token.')
|
||||
->end()
|
||||
->scalarNode('keyset')
|
||||
->isRequired()
|
||||
->info('The encryption keyset. It shall be JWKSet encoded.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
|
||||
private function getTokenExtractorsNode(): ArrayNodeDefinition
|
||||
{
|
||||
$builder = new TreeBuilder('token_extractors');
|
||||
$node = $builder->getRootNode();
|
||||
$node
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->arrayNode('authorization_header')
|
||||
->addDefaultsIfNotSet()
|
||||
->canBeDisabled()
|
||||
->children()
|
||||
->scalarNode('prefix')
|
||||
->defaultValue('Bearer')
|
||||
->end()
|
||||
->scalarNode('name')
|
||||
->defaultValue('Authorization')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('cookie')
|
||||
->addDefaultsIfNotSet()
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->scalarNode('name')
|
||||
->defaultValue('BEARER')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('query_parameter')
|
||||
->addDefaultsIfNotSet()
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->scalarNode('name')
|
||||
->defaultValue('bearer')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('split_cookie')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->arrayNode('cookies')
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct deprecation parameters for setDeprecated.
|
||||
*/
|
||||
private function getDeprecationParameters(string $message, string $version): array
|
||||
{
|
||||
if (method_exists(BaseNode::class, 'getDeprecation')) {
|
||||
return ['lexik/jwt-authentication-bundle', $version, $message];
|
||||
}
|
||||
|
||||
return [$message];
|
||||
}
|
||||
}
|
||||
Vendored
+262
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection;
|
||||
|
||||
use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
|
||||
/**
|
||||
* This is the class that loads and manages your bundle configuration.
|
||||
*
|
||||
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
|
||||
*/
|
||||
class LexikJWTAuthenticationExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$configuration = new Configuration();
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
|
||||
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
|
||||
|
||||
if (method_exists(Alias::class, 'getDeprecation')) {
|
||||
$loader->load('deprecated_51.xml');
|
||||
} else {
|
||||
$loader->load('deprecated.xml');
|
||||
}
|
||||
$loader->load('jwt_manager.xml');
|
||||
$loader->load('key_loader.xml');
|
||||
$loader->load('namshi.xml');
|
||||
$loader->load('lcobucci.xml');
|
||||
$loader->load('response_interceptor.xml');
|
||||
$loader->load('token_authenticator.xml');
|
||||
$loader->load('token_extractor.xml');
|
||||
$loader->load('guard_authenticator.xml');
|
||||
|
||||
if (isset($config['private_key_path'])) {
|
||||
$config['secret_key'] = $config['private_key_path'];
|
||||
$container->setParameter('lexik_jwt_authentication.private_key_path', $config['secret_key']);
|
||||
}
|
||||
|
||||
if (isset($config['public_key_path'])) {
|
||||
$config['public_key'] = $config['public_key_path'];
|
||||
$container->setParameter('lexik_jwt_authentication.public_key_path', $config['public_key']);
|
||||
}
|
||||
|
||||
if (empty($config['public_key']) && empty($config['secret_key'])) {
|
||||
$e = new InvalidConfigurationException('You must either configure a "public_key" or a "secret_key".');
|
||||
$e->setPath('lexik_jwt_authentication');
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$container->setParameter('lexik_jwt_authentication.pass_phrase', $config['pass_phrase']);
|
||||
$container->setParameter('lexik_jwt_authentication.token_ttl', $config['token_ttl']);
|
||||
$container->setParameter('lexik_jwt_authentication.clock_skew', $config['clock_skew']);
|
||||
$container->setParameter('lexik_jwt_authentication.user_identity_field', $config['user_identity_field']);
|
||||
$container->setParameter('lexik_jwt_authentication.allow_no_expiration', $config['allow_no_expiration']);
|
||||
|
||||
$user_id_claim = $config['user_id_claim'] ?: $config['user_identity_field'];
|
||||
$container->setParameter('lexik_jwt_authentication.user_id_claim', $user_id_claim);
|
||||
$encoderConfig = $config['encoder'];
|
||||
|
||||
if ('lexik_jwt_authentication.encoder.default' === $encoderConfig['service']) {
|
||||
@trigger_error('Using "lexik_jwt_authentication.encoder.default" as encoder service is deprecated since LexikJWTAuthenticationBundle 2.5, use "lexik_jwt_authentication.encoder.lcobucci" (default) or your own encoder service instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$container->setAlias('lexik_jwt_authentication.encoder', new Alias($encoderConfig['service'], true));
|
||||
$container->setAlias(JWTEncoderInterface::class, 'lexik_jwt_authentication.encoder');
|
||||
$container->setAlias(
|
||||
'lexik_jwt_authentication.key_loader',
|
||||
new Alias('lexik_jwt_authentication.key_loader.' . ('openssl' === $encoderConfig['crypto_engine'] && 'lexik_jwt_authentication.encoder.default' === $encoderConfig['service'] ? $encoderConfig['crypto_engine'] : 'raw'), true)
|
||||
);
|
||||
|
||||
$container
|
||||
->findDefinition('lexik_jwt_authentication.key_loader')
|
||||
->replaceArgument(0, $config['secret_key'])
|
||||
->replaceArgument(1, $config['public_key']);
|
||||
|
||||
if (isset($config['additional_public_keys'])) {
|
||||
$container
|
||||
->findDefinition('lexik_jwt_authentication.key_loader')
|
||||
->replaceArgument(3, $config['additional_public_keys']);
|
||||
}
|
||||
|
||||
$container->setParameter('lexik_jwt_authentication.encoder.signature_algorithm', $encoderConfig['signature_algorithm']);
|
||||
$container->setParameter('lexik_jwt_authentication.encoder.crypto_engine', $encoderConfig['crypto_engine']);
|
||||
|
||||
$tokenExtractors = $this->createTokenExtractors($container, $config['token_extractors']);
|
||||
$container
|
||||
->getDefinition('lexik_jwt_authentication.extractor.chain_extractor')
|
||||
->replaceArgument(0, $tokenExtractors);
|
||||
|
||||
if (isset($config['remove_token_from_body_when_cookies_used'])) {
|
||||
$container
|
||||
->getDefinition('lexik_jwt_authentication.handler.authentication_success')
|
||||
->replaceArgument(3, $config['remove_token_from_body_when_cookies_used']);
|
||||
}
|
||||
|
||||
if ($config['set_cookies']) {
|
||||
$loader->load('cookie.xml');
|
||||
|
||||
$cookieProviders = [];
|
||||
foreach ($config['set_cookies'] as $name => $attributes) {
|
||||
if ($attributes['partitioned'] && Kernel::VERSION < '6.4') {
|
||||
throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION));
|
||||
}
|
||||
|
||||
$container
|
||||
->setDefinition($id = "lexik_jwt_authentication.cookie_provider.$name", new ChildDefinition('lexik_jwt_authentication.cookie_provider'))
|
||||
->replaceArgument(0, $name)
|
||||
->replaceArgument(1, $attributes['lifetime'] ?? ($config['token_ttl'] ?: 0))
|
||||
->replaceArgument(2, $attributes['samesite'])
|
||||
->replaceArgument(3, $attributes['path'])
|
||||
->replaceArgument(4, $attributes['domain'])
|
||||
->replaceArgument(5, $attributes['secure'])
|
||||
->replaceArgument(6, $attributes['httpOnly'])
|
||||
->replaceArgument(7, $attributes['split'])
|
||||
->replaceArgument(8, $attributes['partitioned']);
|
||||
$cookieProviders[] = new Reference($id);
|
||||
}
|
||||
|
||||
$container
|
||||
->getDefinition('lexik_jwt_authentication.handler.authentication_success')
|
||||
->replaceArgument(2, new IteratorArgument($cookieProviders));
|
||||
}
|
||||
|
||||
if (class_exists(Application::class)) {
|
||||
$loader->load('console.xml');
|
||||
|
||||
$container
|
||||
->getDefinition('lexik_jwt_authentication.generate_keypair_command')
|
||||
->replaceArgument(1, $config['secret_key'])
|
||||
->replaceArgument(2, $config['public_key'])
|
||||
->replaceArgument(3, $config['pass_phrase'])
|
||||
->replaceArgument(4, $encoderConfig['signature_algorithm']);
|
||||
if (!$container->hasParameter('kernel.debug') || !$container->getParameter('kernel.debug')) {
|
||||
$container->removeDefinition('lexik_jwt_authentication.migrate_config_command');
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isConfigEnabled($container, $config['api_platform'])) {
|
||||
if (!class_exists(ApiPlatformBundle::class)) {
|
||||
throw new LogicException('API Platform cannot be detected. Try running "composer require api-platform/core".');
|
||||
}
|
||||
|
||||
$loader->load('api_platform.xml');
|
||||
|
||||
$container
|
||||
->getDefinition('lexik_jwt_authentication.api_platform.openapi.factory')
|
||||
->replaceArgument(1, $config['api_platform']['check_path'] ?? null)
|
||||
->replaceArgument(2, $config['api_platform']['username_path'] ?? null)
|
||||
->replaceArgument(3, $config['api_platform']['password_path'] ?? null);
|
||||
}
|
||||
|
||||
$this->processWithWebTokenConfig($config, $container, $loader);
|
||||
}
|
||||
|
||||
private function createTokenExtractors(ContainerBuilder $container, array $tokenExtractorsConfig): array
|
||||
{
|
||||
$map = [];
|
||||
|
||||
if ($this->isConfigEnabled($container, $tokenExtractorsConfig['authorization_header'])) {
|
||||
$authorizationHeaderExtractorId = 'lexik_jwt_authentication.extractor.authorization_header_extractor';
|
||||
$container
|
||||
->getDefinition($authorizationHeaderExtractorId)
|
||||
->replaceArgument(0, $tokenExtractorsConfig['authorization_header']['prefix'])
|
||||
->replaceArgument(1, $tokenExtractorsConfig['authorization_header']['name']);
|
||||
|
||||
$map[] = new Reference($authorizationHeaderExtractorId);
|
||||
}
|
||||
|
||||
if ($this->isConfigEnabled($container, $tokenExtractorsConfig['query_parameter'])) {
|
||||
$queryParameterExtractorId = 'lexik_jwt_authentication.extractor.query_parameter_extractor';
|
||||
$container
|
||||
->getDefinition($queryParameterExtractorId)
|
||||
->replaceArgument(0, $tokenExtractorsConfig['query_parameter']['name']);
|
||||
|
||||
$map[] = new Reference($queryParameterExtractorId);
|
||||
}
|
||||
|
||||
if ($this->isConfigEnabled($container, $tokenExtractorsConfig['cookie'])) {
|
||||
$cookieExtractorId = 'lexik_jwt_authentication.extractor.cookie_extractor';
|
||||
$container
|
||||
->getDefinition($cookieExtractorId)
|
||||
->replaceArgument(0, $tokenExtractorsConfig['cookie']['name']);
|
||||
|
||||
$map[] = new Reference($cookieExtractorId);
|
||||
}
|
||||
|
||||
if ($this->isConfigEnabled($container, $tokenExtractorsConfig['split_cookie'])) {
|
||||
$cookieExtractorId = 'lexik_jwt_authentication.extractor.split_cookie_extractor';
|
||||
$container
|
||||
->getDefinition($cookieExtractorId)
|
||||
->replaceArgument(0, $tokenExtractorsConfig['split_cookie']['cookies']);
|
||||
|
||||
$map[] = new Reference($cookieExtractorId);
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
private function processWithWebTokenConfig(array $config, ContainerBuilder $container, LoaderInterface $loader): void
|
||||
{
|
||||
if ($config['access_token_issuance']['enabled'] === false && $config['access_token_verification']['enabled'] === false) {
|
||||
return;
|
||||
}
|
||||
$loader->load('web_token.xml');
|
||||
if ($config['access_token_issuance']['enabled'] === true) {
|
||||
$loader->load('web_token_issuance.xml');
|
||||
$accessTokenBuilder = 'lexik_jwt_authentication.access_token_builder';
|
||||
$accessTokenBuilderDefinition = $container->getDefinition($accessTokenBuilder);
|
||||
$accessTokenBuilderDefinition
|
||||
->replaceArgument(3, $config['access_token_issuance']['signature']['algorithm'])
|
||||
->replaceArgument(4, $config['access_token_issuance']['signature']['key'])
|
||||
;
|
||||
if ($config['access_token_issuance']['encryption']['enabled'] === true) {
|
||||
$accessTokenBuilderDefinition
|
||||
->replaceArgument(5, $config['access_token_issuance']['encryption']['key_encryption_algorithm'])
|
||||
->replaceArgument(6, $config['access_token_issuance']['encryption']['content_encryption_algorithm'])
|
||||
->replaceArgument(7, $config['access_token_issuance']['encryption']['key'])
|
||||
;
|
||||
}
|
||||
}
|
||||
if ($config['access_token_verification']['enabled'] === true) {
|
||||
$loader->load('web_token_verification.xml');
|
||||
$accessTokenLoader = 'lexik_jwt_authentication.access_token_loader';
|
||||
$accessTokenLoaderDefinition = $container->getDefinition($accessTokenLoader);
|
||||
$accessTokenLoaderDefinition
|
||||
->replaceArgument(3, $config['access_token_verification']['signature']['claim_checkers'])
|
||||
->replaceArgument(4, $config['access_token_verification']['signature']['header_checkers'])
|
||||
->replaceArgument(5, $config['access_token_verification']['signature']['mandatory_claims'])
|
||||
->replaceArgument(6, $config['access_token_verification']['signature']['allowed_algorithms'])
|
||||
->replaceArgument(7, $config['access_token_verification']['signature']['keyset'])
|
||||
;
|
||||
if ($config['access_token_verification']['encryption']['enabled'] === true) {
|
||||
$accessTokenLoaderDefinition
|
||||
->replaceArgument(8, $config['access_token_verification']['encryption']['continue_on_decryption_failure'])
|
||||
->replaceArgument(9, $config['access_token_verification']['encryption']['header_checkers'])
|
||||
->replaceArgument(10, $config['access_token_verification']['encryption']['allowed_key_encryption_algorithms'])
|
||||
->replaceArgument(11, $config['access_token_verification']['encryption']['allowed_content_encryption_algorithms'])
|
||||
->replaceArgument(12, $config['access_token_verification']['encryption']['keyset'])
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
|
||||
|
||||
if (interface_exists(SecurityFactoryInterface::class) && !interface_exists(AuthenticatorFactoryInterface::class)) {
|
||||
eval('
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
|
||||
|
||||
/**
|
||||
* Wires the "jwt" authenticator from user configuration.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTAuthenticatorFactory implements SecurityFactoryInterface
|
||||
{
|
||||
use JWTAuthenticatorFactoryTrait;
|
||||
}
|
||||
');
|
||||
} elseif (!method_exists(SecurityExtension::class, 'addAuthenticatorFactory')) {
|
||||
eval('
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
|
||||
|
||||
/**
|
||||
* Wires the "jwt" authenticator from user configuration.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTAuthenticatorFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface
|
||||
{
|
||||
use JWTAuthenticatorFactoryTrait;
|
||||
}
|
||||
');
|
||||
} else {
|
||||
/**
|
||||
* Wires the "jwt" authenticator from user configuration.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTAuthenticatorFactory implements AuthenticatorFactoryInterface
|
||||
{
|
||||
use JWTAuthenticatorFactoryTrait;
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Wires the "jwt" authenticator from user configuration.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
trait JWTAuthenticatorFactoryTrait
|
||||
{
|
||||
/**
|
||||
* @throws \LogicException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
|
||||
{
|
||||
throw new \LogicException('This method is implemented for BC purpose and should never be called.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -10;
|
||||
}
|
||||
|
||||
public function getPosition(): string
|
||||
{
|
||||
return 'pre_auth';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'jwt';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('provider')
|
||||
->defaultNull()
|
||||
->end()
|
||||
->scalarNode('authenticator')
|
||||
->defaultValue('lexik_jwt_authentication.security.jwt_authenticator')
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
$authenticatorId = 'security.authenticator.jwt.' . $firewallName;
|
||||
|
||||
$userProviderId = empty($config['provider']) ? $userProviderId : 'security.user.provider.concrete.' . $config['provider'];
|
||||
|
||||
$container
|
||||
->setDefinition($authenticatorId, new ChildDefinition($config['authenticator']))
|
||||
->replaceArgument(3, new Reference($userProviderId))
|
||||
;
|
||||
|
||||
// Compile-time parameter removed by RemoveLegacyAuthenticatorPass
|
||||
// Stop setting it when guard support gets removed (aka when removing Symfony<5.3 support)
|
||||
$container->setParameter('lexik_jwt_authentication.authenticator_manager_enabled', true);
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
|
||||
use Symfony\Component\Config\Definition\BaseNode;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* JWTFactory.
|
||||
*
|
||||
* @deprecated since 2.0, use the "lexik_jwt_authentication.jwt_token_authenticator" Guard
|
||||
* authenticator instead
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
class JWTFactory implements SecurityFactoryInterface
|
||||
{
|
||||
public function __construct($triggerDeprecation = true)
|
||||
{
|
||||
if ($triggerDeprecation) {
|
||||
trigger_deprecation('lexik/jwt-authentication-bundle', '2.0', 'Class "%s" is deprecated, use "%s" instead.', self::class, JWTAuthenticatorFactory::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
|
||||
{
|
||||
$providerId = 'security.authentication.provider.jwt.' . $id;
|
||||
$container
|
||||
->setDefinition($providerId, new ChildDefinition($config['authentication_provider']))
|
||||
->replaceArgument(0, new Reference($userProvider));
|
||||
|
||||
$listenerId = 'security.authentication.listener.jwt.' . $id;
|
||||
$container
|
||||
->setDefinition($listenerId, new ChildDefinition($config['authentication_listener']))
|
||||
->replaceArgument(2, $config);
|
||||
|
||||
$entryPointId = $defaultEntryPoint;
|
||||
|
||||
if ($config['create_entry_point']) {
|
||||
$entryPointId = $this->createEntryPoint($container, $id, $defaultEntryPoint);
|
||||
}
|
||||
|
||||
if ($config['authorization_header']['enabled']) {
|
||||
$authorizationHeaderExtractorId = 'lexik_jwt_authentication.extractor.authorization_header_extractor.' . $id;
|
||||
$container
|
||||
->setDefinition($authorizationHeaderExtractorId, new ChildDefinition('lexik_jwt_authentication.extractor.authorization_header_extractor'))
|
||||
->replaceArgument(0, $config['authorization_header']['prefix'])
|
||||
->replaceArgument(1, $config['authorization_header']['name']);
|
||||
|
||||
$container
|
||||
->getDefinition($listenerId)
|
||||
->addMethodCall('addTokenExtractor', [new Reference($authorizationHeaderExtractorId)]);
|
||||
}
|
||||
|
||||
if ($config['query_parameter']['enabled']) {
|
||||
$queryParameterExtractorId = 'lexik_jwt_authentication.extractor.query_parameter_extractor.' . $id;
|
||||
$container
|
||||
->setDefinition($queryParameterExtractorId, new ChildDefinition('lexik_jwt_authentication.extractor.query_parameter_extractor'))
|
||||
->replaceArgument(0, $config['query_parameter']['name']);
|
||||
|
||||
$container
|
||||
->getDefinition($listenerId)
|
||||
->addMethodCall('addTokenExtractor', [new Reference($queryParameterExtractorId)]);
|
||||
}
|
||||
|
||||
if ($config['cookie']['enabled']) {
|
||||
$cookieExtractorId = 'lexik_jwt_authentication.extractor.cookie_extractor.' . $id;
|
||||
$container
|
||||
->setDefinition($cookieExtractorId, new ChildDefinition('lexik_jwt_authentication.extractor.cookie_extractor'))
|
||||
->replaceArgument(0, $config['cookie']['name']);
|
||||
|
||||
$container
|
||||
->getDefinition($listenerId)
|
||||
->addMethodCall('addTokenExtractor', [new Reference($cookieExtractorId)]);
|
||||
}
|
||||
|
||||
return [$providerId, $listenerId, $entryPointId];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPosition()
|
||||
{
|
||||
return 'pre_auth';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'lexik_jwt';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addConfiguration(NodeDefinition $node)
|
||||
{
|
||||
$deprecationArgs = ['The "%path%.%node%" configuration key is deprecated. Use the "lexik_jwt_authentication.jwt_token_authenticator" Guard authenticator instead.'];
|
||||
if (method_exists(BaseNode::class, 'getDeprecation')) {
|
||||
$deprecationArgs = ['lexik/jwt-authentication-bundle', '2.7', 'The "%path%.%node%" configuration key is deprecated. Use the "lexik_jwt_authentication.jwt_token_authenticator" Guard authenticator instead.'];
|
||||
}
|
||||
|
||||
$node
|
||||
->setDeprecated(...$deprecationArgs)
|
||||
->children()
|
||||
->arrayNode('authorization_header')
|
||||
->addDefaultsIfNotSet()
|
||||
->canBeDisabled()
|
||||
->children()
|
||||
->scalarNode('prefix')
|
||||
->defaultValue('Bearer')
|
||||
->end()
|
||||
->scalarNode('name')
|
||||
->defaultValue('Authorization')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('cookie')
|
||||
->addDefaultsIfNotSet()
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->scalarNode('name')
|
||||
->defaultValue('BEARER')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('query_parameter')
|
||||
->canBeEnabled()
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('name')
|
||||
->defaultValue('bearer')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->booleanNode('throw_exceptions')
|
||||
->defaultFalse()
|
||||
->end()
|
||||
->booleanNode('create_entry_point')
|
||||
->defaultTrue()
|
||||
->end()
|
||||
->scalarNode('authentication_provider')
|
||||
->defaultValue('lexik_jwt_authentication.security.authentication.provider')
|
||||
->end()
|
||||
->scalarNode('authentication_listener')
|
||||
->defaultValue('lexik_jwt_authentication.security.authentication.listener')
|
||||
->end()
|
||||
->end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an entry point, by default it sends a 401 header and ends the request.
|
||||
*
|
||||
* @param string $id
|
||||
* @param mixed $defaultEntryPoint
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function createEntryPoint(ContainerBuilder $container, $id, $defaultEntryPoint)
|
||||
{
|
||||
$entryPointId = 'lexik_jwt_authentication.security.authentication.entry_point.' . $id;
|
||||
$container->setDefinition($entryPointId, new ChildDefinition('lexik_jwt_authentication.security.authentication.entry_point'));
|
||||
|
||||
return $entryPointId;
|
||||
}
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUser;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserInterface;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Creates the `lexik_jwt` user provider.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class JWTUserFactory implements UserProviderFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, $id, $config): void
|
||||
{
|
||||
$container->setDefinition($id, new ChildDefinition('lexik_jwt_authentication.security.jwt_user_provider'))
|
||||
->replaceArgument(0, $config['class']);
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'lexik_jwt';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('class')
|
||||
->cannotBeEmpty()
|
||||
->defaultValue(JWTUser::class)
|
||||
->validate()
|
||||
->ifTrue(function ($class) {
|
||||
return !(new \ReflectionClass($class))->implementsInterface(JWTUserInterface::class);
|
||||
})
|
||||
->thenInvalid('The %s class must implement ' . JWTUserInterface::class . ' for using the "lexik_jwt" user provider.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Encoder;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\JWSProviderInterface;
|
||||
use Namshi\JOSE\JWS;
|
||||
|
||||
/**
|
||||
* Default Json Web Token encoder/decoder.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @deprecated since version 2.5, to be removed in 3.0
|
||||
*/
|
||||
class DefaultEncoder extends LcobucciJWTEncoder
|
||||
{
|
||||
public function __construct(JWSProviderInterface $jwsProvider)
|
||||
{
|
||||
if (!class_exists(JWS::class)) {
|
||||
throw new \RuntimeException('The "namshi/jose" library is deprecated, this bundle does not require it anymore. If you need to keep using it, require it in your composer.json.');
|
||||
}
|
||||
|
||||
@trigger_error(sprintf('The "%s\DefaultEncoder" class is deprecated since version 2.5 and will be removed in 3.0. Use "%s" or create your own encoder instead.', __NAMESPACE__, LcobucciJWTEncoder::class), E_USER_DEPRECATED);
|
||||
|
||||
parent::__construct($jwsProvider);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Encoder;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTEncodeFailureException;
|
||||
|
||||
/**
|
||||
* HeaderAwareJWTEncoderInterface.
|
||||
*/
|
||||
interface HeaderAwareJWTEncoderInterface extends JWTEncoderInterface
|
||||
{
|
||||
/**
|
||||
* @return string the encoded token string
|
||||
*
|
||||
* @throws JWTEncodeFailureException If an error occurred while trying to create
|
||||
* the token (invalid crypto key, invalid payload...)
|
||||
*/
|
||||
public function encode(array $data, array $header = []);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Encoder;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTEncodeFailureException;
|
||||
|
||||
/**
|
||||
* JWTEncoderInterface.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
interface JWTEncoderInterface
|
||||
{
|
||||
/**
|
||||
* @return string the encoded token string
|
||||
*
|
||||
* @throws JWTEncodeFailureException If an error occurred while trying to create
|
||||
* the token (invalid crypto key, invalid payload...)
|
||||
*/
|
||||
public function encode(array $data);
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws JWTDecodeFailureException If an error occurred while trying to load the token
|
||||
* (invalid signature, invalid crypto key, expired token...)
|
||||
*/
|
||||
public function decode($token);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Encoder;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTEncodeFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\JWSProviderInterface;
|
||||
|
||||
/**
|
||||
* Json Web Token encoder/decoder based on the lcobucci/jwt library.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class LcobucciJWTEncoder implements JWTEncoderInterface, HeaderAwareJWTEncoderInterface
|
||||
{
|
||||
/**
|
||||
* @var JWSProviderInterface
|
||||
*/
|
||||
protected $jwsProvider;
|
||||
|
||||
public function __construct(JWSProviderInterface $jwsProvider)
|
||||
{
|
||||
$this->jwsProvider = $jwsProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encode(array $payload, array $header = [])
|
||||
{
|
||||
try {
|
||||
$jws = $this->jwsProvider->create($payload, $header);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new JWTEncodeFailureException(JWTEncodeFailureException::INVALID_CONFIG, 'An error occurred while trying to encode the JWT token. Please verify your configuration (private key/passphrase)', $e, $payload);
|
||||
}
|
||||
|
||||
if (!$jws->isSigned()) {
|
||||
throw new JWTEncodeFailureException(JWTEncodeFailureException::UNSIGNED_TOKEN, 'Unable to create a signed JWT from the given configuration.', null, $payload);
|
||||
}
|
||||
|
||||
return $jws->getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($token)
|
||||
{
|
||||
try {
|
||||
$jws = $this->jwsProvider->load($token);
|
||||
} catch (\Exception $e) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid JWT Token', $e);
|
||||
}
|
||||
|
||||
if ($jws->isInvalid()) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid JWT Token', null, $jws->getPayload());
|
||||
}
|
||||
|
||||
if ($jws->isExpired()) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::EXPIRED_TOKEN, 'Expired JWT Token', null, $jws->getPayload());
|
||||
}
|
||||
|
||||
if (!$jws->isVerified()) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::UNVERIFIED_TOKEN, 'Unable to verify the given JWT through the given configuration. If the "lexik_jwt_authentication.encoder" encryption options have been changed since your last authentication, please renew the token. If the problem persists, verify that the configured keys/passphrase are valid.', null, $jws->getPayload());
|
||||
}
|
||||
|
||||
return $jws->getPayload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Encoder;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTEncodeFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken\AccessTokenBuilder;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken\AccessTokenLoader;
|
||||
|
||||
/**
|
||||
* Json Web Token encoder/decoder based on the web-token framework.
|
||||
*
|
||||
* @author Florent Morsellis <florent.morselli@spomky-labs.com>
|
||||
*/
|
||||
final class WebTokenEncoder implements HeaderAwareJWTEncoderInterface
|
||||
{
|
||||
/**
|
||||
* @var AccessTokenBuilder|null
|
||||
*/
|
||||
private $accessTokenBuilder;
|
||||
|
||||
/**
|
||||
* @var AccessTokenLoader|null
|
||||
*/
|
||||
private $accessTokenLoader;
|
||||
|
||||
public function __construct(?AccessTokenBuilder $accessTokenBuilder, ?AccessTokenLoader $accessTokenLoader)
|
||||
{
|
||||
$this->accessTokenBuilder = $accessTokenBuilder;
|
||||
$this->accessTokenLoader = $accessTokenLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encode(array $payload, array $header = [])
|
||||
{
|
||||
if (!$this->accessTokenBuilder) {
|
||||
throw new \LogicException('The access token issuance features are not enabled.');
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->accessTokenBuilder->build($header, $payload);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new JWTEncodeFailureException(JWTEncodeFailureException::INVALID_CONFIG, 'An error occurred while trying to encode the JWT token. Please verify your configuration (private key/passphrase)', $e, $payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($token)
|
||||
{
|
||||
if (!$this->accessTokenLoader) {
|
||||
throw new \LogicException('The access token verification features are not enabled.');
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->accessTokenLoader->load($token);
|
||||
} catch (JWTFailureException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* AuthenticationFailureEvent.
|
||||
*
|
||||
* @author Emmanuel Vella <vella.emmanuel@gmail.com>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class AuthenticationFailureEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var AuthenticationException
|
||||
*/
|
||||
protected $exception;
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* @var Request|null
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
public function __construct(?AuthenticationException $exception, ?Response $response, ?Request $request = null)
|
||||
{
|
||||
$this->exception = $exception;
|
||||
$this->response = $response;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AuthenticationException
|
||||
*/
|
||||
public function getException()
|
||||
{
|
||||
return $this->exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
public function setResponse(Response $response): void
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function getRequest(): ?Request
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
public function setRequest(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* AuthenticationSuccessEvent.
|
||||
*
|
||||
* @author Dev Lexik <dev@lexik.fr>
|
||||
*/
|
||||
class AuthenticationSuccessEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @var UserInterface
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
public function __construct(array $data, UserInterface $user, Response $response)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->user = $user;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function setData(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserInterface
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
/**
|
||||
* BeforeJWEComputationEvent event is dispatched just before the computation of the encrypted token.
|
||||
* This can be used to add or modify the JWE header parameters.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class BeforeJWEComputationEvent
|
||||
{
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $header;
|
||||
|
||||
public function __construct(array $header)
|
||||
{
|
||||
$this->header = $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setHeader(string $key, $value): self
|
||||
{
|
||||
$this->header[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeHeader(string $key): self
|
||||
{
|
||||
unset($this->header[$key]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getHeader(): array
|
||||
{
|
||||
return $this->header;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* JWTAuthenticatedEvent.
|
||||
*/
|
||||
class JWTAuthenticatedEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $payload;
|
||||
|
||||
/**
|
||||
* @var TokenInterface
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
public function __construct(array $payload, TokenInterface $token)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPayload()
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
public function setPayload(array $payload)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenInterface
|
||||
*/
|
||||
public function getToken()
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* JWTCreatedEvent.
|
||||
*/
|
||||
class JWTCreatedEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $header;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @var UserInterface
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
public function __construct(array $data, UserInterface $user, array $header = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->user = $user;
|
||||
$this->header = $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getHeader()
|
||||
{
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
public function setHeader(array $header)
|
||||
{
|
||||
$this->header = $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function setData(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserInterface
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* JWTDecodedEvent.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
class JWTDecodedEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $payload;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isValid;
|
||||
|
||||
public function __construct(array $payload)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
$this->isValid = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPayload()
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
public function setPayload(array $payload)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark payload as invalid.
|
||||
*/
|
||||
public function markAsInvalid()
|
||||
{
|
||||
$this->isValid = false;
|
||||
$this->stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
return $this->isValid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class JWTEncodedEvent extends Event
|
||||
{
|
||||
private $jwtString;
|
||||
|
||||
public function __construct(string $jwtString)
|
||||
{
|
||||
$this->jwtString = $jwtString;
|
||||
}
|
||||
|
||||
public function getJWTString()
|
||||
{
|
||||
return $this->jwtString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
/**
|
||||
* JWTExpiredEvent.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTExpiredEvent extends AuthenticationFailureEvent implements JWTFailureEventInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* Interface for event classes that are dispatched when a JWT cannot be authenticated.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
interface JWTFailureEventInterface
|
||||
{
|
||||
/**
|
||||
* Gets the response that will be returned after dispatching a
|
||||
* {@link JWTFailureEventInterface} implementation.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getResponse();
|
||||
|
||||
/**
|
||||
* Gets the tied AuthenticationException object.
|
||||
*
|
||||
* @return AuthenticationException
|
||||
*/
|
||||
public function getException();
|
||||
|
||||
/**
|
||||
* Calling this allows to return a custom Response immediately after
|
||||
* the corresponding implementation of this event is dispatched.
|
||||
*/
|
||||
public function setResponse(Response $response);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
/**
|
||||
* JWTInvalidEvent.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTInvalidEvent extends AuthenticationFailureEvent implements JWTFailureEventInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Event;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* JWTNotFoundEvent event is dispatched when a JWT cannot be found in a request
|
||||
* covered by a firewall secured via lexik_jwt.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTNotFoundEvent extends AuthenticationFailureEvent implements JWTFailureEventInterface
|
||||
{
|
||||
public function __construct(AuthenticationException $exception = null, Response $response = null, Request $request = null)
|
||||
{
|
||||
parent::__construct($exception, $response, $request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle;
|
||||
|
||||
/**
|
||||
* Events.
|
||||
*
|
||||
* @author Dev Lexik <dev@lexik.fr>
|
||||
*/
|
||||
final class Events
|
||||
{
|
||||
/**
|
||||
* Dispatched after the token generation to allow sending more data
|
||||
* on the authentication success response.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent")
|
||||
*/
|
||||
public const AUTHENTICATION_SUCCESS = 'lexik_jwt_authentication.on_authentication_success';
|
||||
|
||||
/**
|
||||
* Dispatched after an authentication failure.
|
||||
* Hook into this event to add a custom error message in the response body.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent")
|
||||
*/
|
||||
public const AUTHENTICATION_FAILURE = 'lexik_jwt_authentication.on_authentication_failure';
|
||||
|
||||
/**
|
||||
* Dispatched before the token payload is encoded by the configured encoder (JWTEncoder by default).
|
||||
* Hook into this event to add extra fields to the payload.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent")
|
||||
*/
|
||||
public const JWT_CREATED = 'lexik_jwt_authentication.on_jwt_created';
|
||||
|
||||
/**
|
||||
* Dispatched right after token string is created.
|
||||
* Hook into this event to get token representation itself.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTEncodedEvent")
|
||||
*/
|
||||
public const JWT_ENCODED = 'lexik_jwt_authentication.on_jwt_encoded';
|
||||
|
||||
/**
|
||||
* Dispatched after the token payload has been decoded by the configured encoder (JWTEncoder by default).
|
||||
* Hook into this event to perform additional validation on the received payload.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent")
|
||||
*/
|
||||
public const JWT_DECODED = 'lexik_jwt_authentication.on_jwt_decoded';
|
||||
|
||||
/**
|
||||
* Dispatched after the token payload has been authenticated by the provider.
|
||||
* Hook into this event to perform additional modification to the authenticated token using the payload.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent")
|
||||
*/
|
||||
public const JWT_AUTHENTICATED = 'lexik_jwt_authentication.on_jwt_authenticated';
|
||||
|
||||
/**
|
||||
* Dispatched after the token has been invalidated by the provider.
|
||||
* Hook into this event to add a custom error message in the response body.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent")
|
||||
*/
|
||||
public const JWT_INVALID = 'lexik_jwt_authentication.on_jwt_invalid';
|
||||
|
||||
/**
|
||||
* Dispatched when no token can be found in a request.
|
||||
* Hook into this event to set a custom response.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent")
|
||||
*/
|
||||
public const JWT_NOT_FOUND = 'lexik_jwt_authentication.on_jwt_not_found';
|
||||
|
||||
/**
|
||||
* Dispatched when the token is expired.
|
||||
* The expired token's payload can be retrieved by hooking into this event, so you can set a different
|
||||
* response.
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent")
|
||||
*/
|
||||
public const JWT_EXPIRED = 'lexik_jwt_authentication.on_jwt_expired';
|
||||
|
||||
/**
|
||||
* Dispatched before the JWE is computed.
|
||||
* This event allow the JWE header parameters to be changed.
|
||||
* It is only dispatched when using Web-Token
|
||||
*
|
||||
* @Event("Lexik\Bundle\JWTAuthenticationBundle\Event\BeforeJWEComputationEvent")
|
||||
*/
|
||||
public const BEFORE_JWE_COMPUTATION = 'lexik_jwt_authentication.before_jwe_computation';
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* Exception that should be thrown from an authenticator during the authentication process
|
||||
* if a token is expired.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class ExpiredTokenException extends AuthenticationException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageKey(): string
|
||||
{
|
||||
return 'Expired JWT Token';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* Missing key in the token payload during authentication.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class InvalidPayloadException extends AuthenticationException
|
||||
{
|
||||
private $invalidKey;
|
||||
|
||||
/**
|
||||
* @param string $invalidKey The key that cannot be found in the payload
|
||||
*/
|
||||
public function __construct(string $invalidKey)
|
||||
{
|
||||
$this->invalidKey = $invalidKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageKey(): string
|
||||
{
|
||||
return sprintf('Unable to find key "%s" in the token payload.', $this->invalidKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* Exception to be thrown in case of invalid token during an authentication process.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class InvalidTokenException extends AuthenticationException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageKey(): string
|
||||
{
|
||||
return 'Invalid JWT Token';
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception;
|
||||
|
||||
/**
|
||||
* JWTDecodeFailureException is thrown if an error occurs in the token decoding process.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTDecodeFailureException extends JWTFailureException
|
||||
{
|
||||
public const INVALID_TOKEN = 'invalid_token';
|
||||
|
||||
public const UNVERIFIED_TOKEN = 'unverified_token';
|
||||
|
||||
public const EXPIRED_TOKEN = 'expired_token';
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception;
|
||||
|
||||
/**
|
||||
* JWTEncodeFailureException is thrown if an error occurs in the token encoding process.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTEncodeFailureException extends JWTFailureException
|
||||
{
|
||||
public const INVALID_CONFIG = 'invalid_config';
|
||||
|
||||
public const UNSIGNED_TOKEN = 'unsigned_token';
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception;
|
||||
|
||||
/**
|
||||
* Base class for exceptions thrown during JWT creation/loading.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTFailureException extends \Exception
|
||||
{
|
||||
private $reason;
|
||||
private $payload;
|
||||
|
||||
public function __construct(string $reason, string $message, \Throwable $previous = null, array $payload = null)
|
||||
{
|
||||
$this->reason = $reason;
|
||||
$this->payload = $payload;
|
||||
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
|
||||
public function getReason()
|
||||
{
|
||||
return $this->reason;
|
||||
}
|
||||
|
||||
public function getPayload()
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* Exception to be thrown in case of invalid token during an authentication process.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class MissingTokenException extends AuthenticationException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageKey(): string
|
||||
{
|
||||
return 'JWT Token not found';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* User not found during authentication.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class UserNotFoundException extends AuthenticationException
|
||||
{
|
||||
private $userIdentityField;
|
||||
private $identity;
|
||||
|
||||
public function __construct(string $userIdentityField, string $identity)
|
||||
{
|
||||
$this->userIdentityField = $userIdentityField;
|
||||
$this->identity = $identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMessageKey(): string
|
||||
{
|
||||
return sprintf('Unable to load an user with property "%s" = "%s". If the user identity has changed, you must renew the token. Otherwise, verify that the "lexik_jwt_authentication.user_identity_field" config option is correctly set.', $this->userIdentityField, $this->identity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Helper;
|
||||
|
||||
/**
|
||||
* JWTSplitter.
|
||||
*
|
||||
* @author Adam Lukacovic <adam@adamlukacovic.sk>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class JWTSplitter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $header;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $payload;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $signature;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $jwt;
|
||||
|
||||
public function __construct(string $jwt)
|
||||
{
|
||||
$this->jwt = $jwt;
|
||||
[$this->header, $this->payload, $this->signature] = explode('.', $jwt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parts
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getParts($parts = [])
|
||||
{
|
||||
if (!$parts) {
|
||||
return $this->jwt;
|
||||
}
|
||||
|
||||
return implode('.', array_intersect_key(get_object_vars($this), array_flip($parts)));
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2014-2020 Lexik <dev@lexik.fr>, Robin Chalas <robin.chalas@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler\ApiPlatformOpenApiPass;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler\DeprecateLegacyGuardAuthenticatorPass;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler\RegisterLegacyGuardAuthenticatorPass;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler\WireGenerateTokenCommandPass;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory\JWTAuthenticatorFactory;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory\JWTFactory;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory\JWTSecurityFactory;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory\JWTUserFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* LexikJWTAuthenticationBundle.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
class LexikJWTAuthenticationBundle extends Bundle
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(ContainerBuilder $container): void
|
||||
{
|
||||
parent::build($container);
|
||||
|
||||
$container->addCompilerPass(new WireGenerateTokenCommandPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
|
||||
$container->addCompilerPass(new DeprecateLegacyGuardAuthenticatorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
|
||||
$container->addCompilerPass(new ApiPlatformOpenApiPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
|
||||
|
||||
/** @var SecurityExtension $extension */
|
||||
$extension = $container->getExtension('security');
|
||||
|
||||
$extension->addUserProviderFactory(new JWTUserFactory());
|
||||
|
||||
// Authenticator factory for Symfony 5.4 and later
|
||||
if (method_exists($extension, 'addAuthenticatorFactory')) {
|
||||
$extension->addAuthenticatorFactory(new JWTAuthenticatorFactory());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Security listener factory for Symfony 5.3 and earlier
|
||||
if (method_exists($extension, 'addSecurityListenerFactory')) {
|
||||
$extension->addSecurityListenerFactory(new JWTAuthenticatorFactory());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Security listener factory for Symfony 4.4
|
||||
if (method_exists($extension, 'addSecurityListenerFactory')) {
|
||||
$extension->addSecurityListenerFactory(new JWTFactory(false)); // BC 1.x, to be removed in 3.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\OpenApi;
|
||||
|
||||
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
|
||||
use ApiPlatform\OpenApi\Model\MediaType;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\PathItem;
|
||||
use ApiPlatform\OpenApi\Model\RequestBody;
|
||||
use ApiPlatform\OpenApi\OpenApi;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Decorates API Platform OpenApiFactory.
|
||||
*
|
||||
* @author Vincent Chalamon <vincentchalamon@gmail.com>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class OpenApiFactory implements OpenApiFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var OpenApiFactoryInterface
|
||||
*/
|
||||
private $decorated;
|
||||
|
||||
private $checkPath;
|
||||
private $usernamePath;
|
||||
private $passwordPath;
|
||||
|
||||
public function __construct(OpenApiFactoryInterface $decorated, string $checkPath, string $usernamePath, string $passwordPath)
|
||||
{
|
||||
$this->decorated = $decorated;
|
||||
$this->checkPath = $checkPath;
|
||||
$this->usernamePath = $usernamePath;
|
||||
$this->passwordPath = $passwordPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __invoke(array $context = []): OpenApi
|
||||
{
|
||||
$openApi = ($this->decorated)($context);
|
||||
|
||||
$openApi
|
||||
->getComponents()->getSecuritySchemes()->offsetSet(
|
||||
'JWT',
|
||||
new \ArrayObject([
|
||||
'type' => 'http',
|
||||
'scheme' => 'bearer',
|
||||
'bearerFormat' => 'JWT',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$openApi
|
||||
->getPaths()
|
||||
->addPath($this->checkPath, (new PathItem())->withPost(
|
||||
(new Operation())
|
||||
->withOperationId('login_check_post')
|
||||
->withTags(['Login Check'])
|
||||
->withResponses([
|
||||
Response::HTTP_OK => [
|
||||
'description' => 'User token created',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'token' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
'nullable' => false,
|
||||
],
|
||||
],
|
||||
'required' => ['token'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
->withSummary('Creates a user token.')
|
||||
->withDescription('Creates a user token.')
|
||||
->withRequestBody(
|
||||
(new RequestBody())
|
||||
->withDescription('The login data')
|
||||
->withContent(new \ArrayObject([
|
||||
'application/json' => new MediaType(new \ArrayObject(new \ArrayObject([
|
||||
'type' => 'object',
|
||||
'properties' => $properties = array_merge_recursive($this->getJsonSchemaFromPathParts(explode('.', $this->usernamePath)), $this->getJsonSchemaFromPathParts(explode('.', $this->passwordPath))),
|
||||
'required' => array_keys($properties),
|
||||
]))),
|
||||
]))
|
||||
->withRequired(true)
|
||||
)
|
||||
));
|
||||
|
||||
return $openApi;
|
||||
}
|
||||
|
||||
private function getJsonSchemaFromPathParts(array $pathParts): array
|
||||
{
|
||||
$jsonSchema = [];
|
||||
|
||||
if (count($pathParts) === 1) {
|
||||
$jsonSchema[array_shift($pathParts)] = [
|
||||
'type' => 'string',
|
||||
'nullable' => false,
|
||||
];
|
||||
|
||||
return $jsonSchema;
|
||||
}
|
||||
|
||||
$pathPart = array_shift($pathParts);
|
||||
$properties = $this->getJsonSchemaFromPathParts($pathParts);
|
||||
$jsonSchema[$pathPart] = [
|
||||
'type' => 'object',
|
||||
'properties' => $properties,
|
||||
'required' => array_keys($properties),
|
||||
];
|
||||
|
||||
return $jsonSchema;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.api_platform.openapi.factory" class="Lexik\Bundle\JWTAuthenticationBundle\OpenApi\OpenApiFactory" decorates="api_platform.openapi.factory" decoration-on-invalid="ignore" public="false">
|
||||
<argument type="service" id="lexik_jwt_authentication.api_platform.openapi.factory.inner"/>
|
||||
<argument /><!-- check path -->
|
||||
<argument /><!-- username path -->
|
||||
<argument /><!-- password path -->
|
||||
</service>
|
||||
</services>
|
||||
|
||||
</container>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.check_config_command" class="Lexik\Bundle\JWTAuthenticationBundle\Command\CheckConfigCommand">
|
||||
<argument type="service" id="lexik_jwt_authentication.key_loader" />
|
||||
<argument type="string">%lexik_jwt_authentication.encoder.signature_algorithm%</argument> <!-- signature algorithm -->
|
||||
<tag name="console.command" command="lexik:jwt:check-config" />
|
||||
</service>
|
||||
|
||||
<service id="lexik_jwt_authentication.migrate_config_command" class="Lexik\Bundle\JWTAuthenticationBundle\Command\MigrateConfigCommand">
|
||||
<argument type="service" id="lexik_jwt_authentication.key_loader" />
|
||||
<argument type="string">%lexik_jwt_authentication.pass_phrase%</argument>
|
||||
<argument type="string">%lexik_jwt_authentication.encoder.signature_algorithm%</argument>
|
||||
<tag name="console.command" command="lexik:jwt:migrate-config" />
|
||||
</service>
|
||||
|
||||
<service id="lexik_jwt_authentication.enable_encryption_config_command" class="Lexik\Bundle\JWTAuthenticationBundle\Command\EnableEncryptionConfigCommand">
|
||||
<argument type="service" id="Jose\Component\Core\AlgorithmManagerFactory" on-invalid="null" />
|
||||
<tag name="console.command" command="lexik:jwt:enable-encryption" />
|
||||
</service>
|
||||
|
||||
<service id="lexik_jwt_authentication.generate_token_command" class="Lexik\Bundle\JWTAuthenticationBundle\Command\GenerateTokenCommand" public="true">
|
||||
<argument type="service" id="lexik_jwt_authentication.jwt_manager" />
|
||||
<argument type="collection" /> <!-- user providers -->
|
||||
<tag name="console.command" command="lexik:jwt:generate-token" />
|
||||
</service>
|
||||
|
||||
<service id="lexik_jwt_authentication.generate_keypair_command" class="Lexik\Bundle\JWTAuthenticationBundle\Command\GenerateKeyPairCommand">
|
||||
<argument type="service" id="filesystem" />
|
||||
<argument />
|
||||
<argument />
|
||||
<argument />
|
||||
<argument />
|
||||
<tag name="console.command" command="lexik:jwt:generate-keypair" />
|
||||
</service>
|
||||
</services>
|
||||
|
||||
</container>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.cookie_provider" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Cookie\JWTCookieProvider" abstract="true">
|
||||
<argument>null</argument> <!-- Default name -->
|
||||
<argument>null</argument> <!-- Default lifetime -->
|
||||
<argument/> <!-- Default samesite -->
|
||||
<argument/> <!-- Default path -->
|
||||
<argument>null</argument> <!-- Default domain -->
|
||||
<argument/> <!-- Default secure -->
|
||||
<argument/> <!-- Default httpOnly -->
|
||||
<argument>null</argument> <!-- Default split -->
|
||||
<argument>false</argument> <!-- Default partitioned -->
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<!-- Deprecated -->
|
||||
<service id="lexik_jwt_authentication.security.authentication.provider" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Provider\JWTProvider" public="false">
|
||||
<argument /> <!-- User Provider -->
|
||||
<argument type="service" id="lexik_jwt_authentication.jwt_manager" />
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
<argument>%lexik_jwt_authentication.user_id_claim%</argument>
|
||||
<call method="setUserIdentityField">
|
||||
<argument>%lexik_jwt_authentication.user_identity_field%</argument>
|
||||
</call>
|
||||
<deprecated>The "%service_id%" service is deprecated since LexikJWTAuthenticationBundle version 2.0 and will be removed in 3.0</deprecated>
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.security.authentication.listener" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall\JWTListener" public="false">
|
||||
<argument type="service" id="security.token_storage"/>
|
||||
<argument type="service" id="security.authentication.manager"/>
|
||||
<argument /> <!-- Options -->
|
||||
<call method="setDispatcher">
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
</call>
|
||||
<deprecated>The "%service_id%" service is deprecated since LexikJWTAuthenticationBundle version 2.0 and will be removed in 3.0</deprecated>
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.security.authentication.entry_point" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Http\EntryPoint\JWTEntryPoint" public="false">
|
||||
<deprecated>The "%service_id%" service is deprecated since LexikJWTAuthenticationBundle version 2.0 and will be removed in 3.0</deprecated>
|
||||
</service>
|
||||
<service id="Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenInterface" alias="lexik_jwt_authentication.jwt_manager" />
|
||||
|
||||
<service id="lexik_jwt_authentication.key_loader.openssl" class="Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\OpenSSLKeyLoader" parent="lexik_jwt_authentication.key_loader.abstract">
|
||||
<deprecated>The "%service_id%" service is deprecated since version 2.5 and will be removed in 3.0. Use lexik_jwt_authentication.key_loader.raw instead.</deprecated>
|
||||
</service>
|
||||
|
||||
<service id="lexik_jwt_authentication.jws_provider.default" class="Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\DefaultJWSProvider" public="false">
|
||||
<deprecated>The "%service_id%" is deprecated since version 2.5 and will be removed in 5.0, use "lexik_jwt_authentication.jws_provider.lcobucci" instead.</deprecated>
|
||||
<argument type="service" id="lexik_jwt_authentication.key_loader"/>
|
||||
<argument>%lexik_jwt_authentication.encoder.crypto_engine%</argument>
|
||||
<argument>%lexik_jwt_authentication.encoder.signature_algorithm%</argument>
|
||||
<argument>%lexik_jwt_authentication.token_ttl%</argument>
|
||||
<argument>%lexik_jwt_authentication.clock_skew%</argument>
|
||||
</service>
|
||||
</services>
|
||||
|
||||
</container>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<!-- Updated deprecation element for Symfony 5.1+ -->
|
||||
<!-- Deprecated -->
|
||||
<service id="lexik_jwt_authentication.security.authentication.provider" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Provider\JWTProvider" public="false">
|
||||
<argument /> <!-- User Provider -->
|
||||
<argument type="service" id="lexik_jwt_authentication.jwt_manager" />
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
<argument>%lexik_jwt_authentication.user_id_claim%</argument>
|
||||
<call method="setUserIdentityField">
|
||||
<argument>%lexik_jwt_authentication.user_identity_field%</argument>
|
||||
</call>
|
||||
<deprecated package="lexik/jwt-authentication-bundle" version="2.0">The "%service_id%" service is deprecated since LexikJWTAuthenticationBundle version 2.0 and will be removed in 3.0</deprecated>
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.security.authentication.listener" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall\JWTListener" public="false">
|
||||
<argument type="service" id="security.token_storage"/>
|
||||
<argument type="service" id="security.authentication.manager"/>
|
||||
<argument /> <!-- Options -->
|
||||
<call method="setDispatcher">
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
</call>
|
||||
<deprecated package="lexik/jwt-authentication-bundle" version="2.0">The "%service_id%" service is deprecated since LexikJWTAuthenticationBundle version 2.0 and will be removed in 3.0</deprecated>
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.security.authentication.entry_point" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Http\EntryPoint\JWTEntryPoint" public="false">
|
||||
<deprecated package="lexik/jwt-authentication-bundle" version="2.0">The "%service_id%" service is deprecated since LexikJWTAuthenticationBundle version 2.0 and will be removed in 3.0</deprecated>
|
||||
</service>
|
||||
<service id="Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenInterface" alias="lexik_jwt_authentication.jwt_manager" />
|
||||
|
||||
<service id="lexik_jwt_authentication.key_loader.openssl" class="Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\OpenSSLKeyLoader" parent="lexik_jwt_authentication.key_loader.abstract">
|
||||
<deprecated package="lexik/jwt-authentication-bundle" version="2.5">The "%service_id%" service is deprecated since version 2.5 and will be removed in 3.0. Use lexik_jwt_authentication.key_loader.raw instead.</deprecated>
|
||||
</service>
|
||||
|
||||
<service id="lexik_jwt_authentication.jws_provider.default" class="Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\DefaultJWSProvider" public="false">
|
||||
<deprecated package="lexik/jwt-authentication-bundle" version="2.5">The "%service_id%" is deprecated since version 2.5 and will be removed in 5.0, use "lexik_jwt_authentication.jws_provider.lcobucci" instead.</deprecated>
|
||||
<argument type="service" id="lexik_jwt_authentication.key_loader"/>
|
||||
<argument>%lexik_jwt_authentication.encoder.crypto_engine%</argument>
|
||||
<argument>%lexik_jwt_authentication.encoder.signature_algorithm%</argument>
|
||||
<argument>%lexik_jwt_authentication.token_ttl%</argument>
|
||||
<argument>%lexik_jwt_authentication.clock_skew%</argument>
|
||||
</service>
|
||||
</services>
|
||||
|
||||
</container>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.jwt_token_authenticator" alias="lexik_jwt_authentication.security.guard.jwt_token_authenticator" />
|
||||
|
||||
<service id="lexik_jwt_authentication.security.guard.jwt_token_authenticator" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Guard\JWTTokenAuthenticator">
|
||||
<argument type="service" id="lexik_jwt_authentication.jwt_manager"/>
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
<argument type="service" id="lexik_jwt_authentication.extractor.chain_extractor"/>
|
||||
<argument type="service">
|
||||
<service class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" />
|
||||
</argument>
|
||||
<argument type="service" id="translator" on-invalid="null" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.jwt_manager" class="Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManager" public="true">
|
||||
<argument type="service" id="lexik_jwt_authentication.encoder"/>
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
<argument>%lexik_jwt_authentication.user_id_claim%</argument>
|
||||
<call method="setUserIdentityField">
|
||||
<argument>%lexik_jwt_authentication.user_identity_field%</argument>
|
||||
<argument>false</argument>
|
||||
</call>
|
||||
</service>
|
||||
|
||||
<service id="Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface" alias="lexik_jwt_authentication.jwt_manager" />
|
||||
</services>
|
||||
</container>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.key_loader.abstract" abstract="true" public="false">
|
||||
<argument/> <!-- private key -->
|
||||
<argument/> <!-- public key -->
|
||||
<argument>%lexik_jwt_authentication.pass_phrase%</argument>
|
||||
<argument type="collection" /> <!-- additional public keys -->
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.key_loader.raw" class="Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\RawKeyLoader" parent="lexik_jwt_authentication.key_loader.abstract"/>
|
||||
</services>
|
||||
</container>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.encoder.lcobucci" class="Lexik\Bundle\JWTAuthenticationBundle\Encoder\LcobucciJWTEncoder">
|
||||
<argument type="service" id="lexik_jwt_authentication.jws_provider.lcobucci" />
|
||||
</service>
|
||||
|
||||
<service id="Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\JWSProviderInterface" alias="lexik_jwt_authentication.jws_provider.lcobucci" />
|
||||
<service id="lexik_jwt_authentication.jws_provider.lcobucci" class="Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\LcobucciJWSProvider" public="false">
|
||||
<argument type="service" id="lexik_jwt_authentication.key_loader.raw"/>
|
||||
<argument>%lexik_jwt_authentication.encoder.crypto_engine%</argument>
|
||||
<argument>%lexik_jwt_authentication.encoder.signature_algorithm%</argument>
|
||||
<argument>%lexik_jwt_authentication.token_ttl%</argument>
|
||||
<argument>%lexik_jwt_authentication.clock_skew%</argument>
|
||||
<argument>%lexik_jwt_authentication.allow_no_expiration%</argument>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.encoder.default" class="Lexik\Bundle\JWTAuthenticationBundle\Encoder\DefaultEncoder">
|
||||
<argument type="service" id="lexik_jwt_authentication.jws_provider.default"/>
|
||||
</service>
|
||||
|
||||
<service id="Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\JWSProviderInterface" alias="lexik_jwt_authentication.jws_provider.default" />
|
||||
</services>
|
||||
</container>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.handler.authentication_success" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler">
|
||||
<argument type="service" id="lexik_jwt_authentication.jwt_manager"/>
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
<argument type="collection"/> <!-- Cookie providers -->
|
||||
<argument>true</argument>
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
</service>
|
||||
<service id="Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler" alias="lexik_jwt_authentication.handler.authentication_success" />
|
||||
|
||||
<service id="lexik_jwt_authentication.handler.authentication_failure" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationFailureHandler">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
<argument type="service" id="translator" on-invalid="null" />
|
||||
</service>
|
||||
<service id="Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationFailureHandler" alias="lexik_jwt_authentication.handler.authentication_failure" />
|
||||
</services>
|
||||
</container>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.security.jwt_authenticator" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\JWTAuthenticator" abstract="true">
|
||||
<argument type="service" id="lexik_jwt_authentication.jwt_manager"/>
|
||||
<argument type="service" id="event_dispatcher"/>
|
||||
<argument type="service" id="lexik_jwt_authentication.extractor.chain_extractor"/>
|
||||
<argument /> <!-- User Provider -->
|
||||
<argument type="service" id="translator" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
</services>
|
||||
</container>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.extractor.chain_extractor" class="Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\ChainTokenExtractor" public="false">
|
||||
<argument type="collection" />
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.extractor.authorization_header_extractor" class="Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\AuthorizationHeaderTokenExtractor">
|
||||
<argument /> <!-- Header Value Prefix -->
|
||||
<argument /> <!-- Header Value Name -->
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.extractor.query_parameter_extractor" class="Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\QueryParameterTokenExtractor">
|
||||
<argument /> <!-- Parameter Name -->
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.extractor.cookie_extractor" class="Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\CookieTokenExtractor">
|
||||
<argument /> <!-- Name -->
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.extractor.split_cookie_extractor" class="Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\SplitCookieExtractor">
|
||||
<argument /> <!-- Cookies -->
|
||||
</service>
|
||||
<service public="false" id="lexik_jwt_authentication.security.jwt_user_provider" class="Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserProvider">
|
||||
<argument />
|
||||
</service>
|
||||
<service id="Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface" alias="lexik_jwt_authentication.extractor.chain_extractor" />
|
||||
</services>
|
||||
</container>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.encoder.web_token" class="Lexik\Bundle\JWTAuthenticationBundle\Encoder\WebTokenEncoder" public="false">
|
||||
<argument type="service" id="lexik_jwt_authentication.access_token_builder" on-invalid="null" />
|
||||
<argument type="service" id="lexik_jwt_authentication.access_token_loader" on-invalid="null" />
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.subscriber.access_token_time" class="Lexik\Bundle\JWTAuthenticationBundle\Subscriber\AdditionalAccessTokenClaimsAndHeaderSubscriber" public="false">
|
||||
<argument on-invalid="null">%lexik_jwt_authentication.token_ttl%</argument>
|
||||
<tag name="kernel.event_subscriber" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.access_token_builder" class="Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken\AccessTokenBuilder" public="false">
|
||||
<argument type="service" id="Symfony\Contracts\EventDispatcher\EventDispatcherInterface" />
|
||||
<argument type="service" id="Jose\Bundle\JoseFramework\Services\JWSBuilderFactory" />
|
||||
<argument type="service" id="Jose\Bundle\JoseFramework\Services\JWEBuilderFactory" on-invalid="null" />
|
||||
<argument /> <!-- Signature algorithm -->
|
||||
<argument /> <!-- Signature key -->
|
||||
<argument on-invalid="null" /> <!-- Key encryption algorithm -->
|
||||
<argument on-invalid="null" /> <!-- Content encryption algorithm -->
|
||||
<argument on-invalid="null" /> <!-- Encryption key -->
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="lexik_jwt_authentication.access_token_loader" class="Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken\AccessTokenLoader" public="false">
|
||||
<argument type="service" id="Jose\Bundle\JoseFramework\Services\JWSLoaderFactory" />
|
||||
<argument type="service" id="Jose\Bundle\JoseFramework\Services\JWELoaderFactory" on-invalid="null" />
|
||||
<argument type="service" id="Jose\Bundle\JoseFramework\Services\ClaimCheckerManagerFactory" />
|
||||
<argument type="collection" /> <!-- Claim checkers -->
|
||||
<argument type="collection"/> <!-- JWS header checkers -->
|
||||
<argument type="collection"/> <!-- Mandatory claims -->
|
||||
<argument type="collection" /> <!-- Allowed signature algorithms -->
|
||||
<argument /> <!-- Signature keyset -->
|
||||
<argument on-invalid="null" /> <!-- Continue on decryption failure -->
|
||||
<argument type="collection" on-invalid="null" /> <!-- JWE header checkers -->
|
||||
<argument type="collection" on-invalid="null" /> <!-- Allowed key encryption algorithms -->
|
||||
<argument type="collection" on-invalid="null" /> <!-- Allowed content encryption algorithms -->
|
||||
<argument on-invalid="null" /> <!-- Encryption keyset -->
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.web_token.iat_validator" class="Jose\Component\Checker\IssuedAtChecker" public="false">
|
||||
<argument>%lexik_jwt_authentication.clock_skew%</argument>
|
||||
<argument>true</argument>
|
||||
<tag name="jose.checker.claim" alias="iat_with_clock_skew" />
|
||||
<tag name="jose.checker.header" alias="iat_with_clock_skew" />
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.web_token.exp_validator" class="Jose\Component\Checker\ExpirationTimeChecker" public="false">
|
||||
<argument>%lexik_jwt_authentication.clock_skew%</argument>
|
||||
<argument>true</argument>
|
||||
<tag name="jose.checker.claim" alias="exp_with_clock_skew" />
|
||||
<tag name="jose.checker.header" alias="exp_with_clock_skew" />
|
||||
</service>
|
||||
<service id="lexik_jwt_authentication.web_token.nbf_validator" class="Jose\Component\Checker\NotBeforeChecker" public="false">
|
||||
<argument>%lexik_jwt_authentication.clock_skew%</argument>
|
||||
<argument>true</argument>
|
||||
<tag name="jose.checker.claim" alias="nbf_with_clock_skew" />
|
||||
<tag name="jose.checker.header" alias="nbf_with_clock_skew" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Response;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
/**
|
||||
* JWTAuthenticationFailureResponse.
|
||||
*
|
||||
* Response sent on failed JWT authentication (can be replaced by a custom Response).
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class JWTAuthenticationFailureResponse extends JWTCompatAuthenticationFailureResponse
|
||||
{
|
||||
private $message;
|
||||
|
||||
public function __construct(string $message = 'Bad credentials', int $statusCode = JsonResponse::HTTP_UNAUTHORIZED)
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
parent::__construct(null, $statusCode, ['WWW-Authenticate' => 'Bearer']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the failure message.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return JWTAuthenticationFailureResponse
|
||||
*/
|
||||
public function setMessage($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
$this->setData();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the failure message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Response;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
/**
|
||||
* Response sent on successful JWT authentication.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class JWTAuthenticationSuccessResponse extends JsonResponse
|
||||
{
|
||||
/**
|
||||
* @param string $token Json Web Token
|
||||
* @param array $data Extra data passed to the response
|
||||
*/
|
||||
public function __construct($token, array $data = [], array $jwtCookies = [])
|
||||
{
|
||||
if (!$jwtCookies) {
|
||||
parent::__construct(['token' => $token] + $data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
parent::__construct($data);
|
||||
|
||||
foreach ($jwtCookies as $cookie) {
|
||||
$this->headers->setCookie($cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Response;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
/**
|
||||
* The "AND" in the if statement is a temporary fix for the following issue:
|
||||
* https://github.com/lexik/LexikJWTAuthenticationBundle/issues/944
|
||||
* https://github.com/vimeo/psalm/issues/7923
|
||||
*/
|
||||
if (80000 <= \PHP_VERSION_ID and (new \ReflectionMethod(JsonResponse::class, 'setData'))->hasReturnType()) {
|
||||
eval('
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Response;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
/**
|
||||
* Compatibility layer for Symfony 6.0 and later.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class JWTCompatAuthenticationFailureResponse extends JsonResponse
|
||||
{
|
||||
/**
|
||||
* Sets the response data with the statusCode & message included.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setData($data = []): static
|
||||
{
|
||||
return parent::setData((array)$data + ["code" => $this->statusCode, "message" => $this->getMessage()]);
|
||||
}
|
||||
}
|
||||
');
|
||||
} else {
|
||||
/**
|
||||
* Compatibility layer for Symfony 5.4 and earlier.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class JWTCompatAuthenticationFailureResponse extends JsonResponse
|
||||
{
|
||||
/**
|
||||
* Sets the response data with the statusCode & message included.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setData($data = [])
|
||||
{
|
||||
return parent::setData((array)$data + ['code' => $this->statusCode, 'message' => $this->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Provider;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\JWTAuthenticator;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManagerInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* JWTProvider.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*
|
||||
* @deprecated since 2.0, will be removed in 3.0. See
|
||||
* {@link JWTAuthenticator} instead
|
||||
*/
|
||||
class JWTProvider implements AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var UserProviderInterface
|
||||
*/
|
||||
protected $userProvider;
|
||||
|
||||
/**
|
||||
* @var JWTManagerInterface
|
||||
*/
|
||||
protected $jwtManager;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $userIdentityField;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $userIdClaim;
|
||||
|
||||
/**
|
||||
* @param string $userIdClaim
|
||||
*/
|
||||
public function __construct(
|
||||
UserProviderInterface $userProvider,
|
||||
JWTManagerInterface $jwtManager,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
$userIdClaim
|
||||
) {
|
||||
@trigger_error(sprintf('The "%s" class is deprecated since version 2.0 and will be removed in 3.0. See "%s" instead.', self::class, JWTTokenAuthenticator::class), E_USER_DEPRECATED);
|
||||
|
||||
$this->userProvider = $userProvider;
|
||||
$this->jwtManager = $jwtManager;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->userIdentityField = 'username';
|
||||
$this->userIdClaim = $userIdClaim;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate(TokenInterface $token)
|
||||
{
|
||||
try {
|
||||
if (!$payload = $this->jwtManager->decode($token)) {
|
||||
throw $this->createAuthenticationException();
|
||||
}
|
||||
} catch (JWTDecodeFailureException $e) {
|
||||
throw $this->createAuthenticationException($e);
|
||||
}
|
||||
|
||||
$user = $this->getUserFromPayload($payload);
|
||||
|
||||
$authToken = new JWTUserToken($user->getRoles());
|
||||
$authToken->setUser($user);
|
||||
$authToken->setRawToken($token->getCredentials());
|
||||
|
||||
$event = new JWTAuthenticatedEvent($payload, $authToken);
|
||||
$this->dispatcher->dispatch($event, Events::JWT_AUTHENTICATED);
|
||||
|
||||
return $authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load user from payload, using username by default.
|
||||
* Override this to load by another property.
|
||||
*
|
||||
* @return UserInterface
|
||||
*/
|
||||
protected function getUserFromPayload(array $payload)
|
||||
{
|
||||
if (!isset($payload[$this->userIdClaim])) {
|
||||
throw $this->createAuthenticationException();
|
||||
}
|
||||
|
||||
if (method_exists($this->userProvider, 'loadUserByIdentifier')) {
|
||||
return $this->userProvider->loadUserByIdentifier($payload[$this->userIdClaim]);
|
||||
}
|
||||
|
||||
return $this->userProvider->loadUserByUsername($payload[$this->userIdClaim]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supports(TokenInterface $token)
|
||||
{
|
||||
return $token instanceof JWTUserToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserIdentityField()
|
||||
{
|
||||
return $this->userIdentityField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userIdentityField
|
||||
*/
|
||||
public function setUserIdentityField($userIdentityField)
|
||||
{
|
||||
$this->userIdentityField = $userIdentityField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserIdClaim()
|
||||
{
|
||||
return $this->userIdClaim;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JWTDecodeFailureException $previous
|
||||
*
|
||||
* @return AuthenticationException
|
||||
*/
|
||||
private function createAuthenticationException(JWTDecodeFailureException $previous = null)
|
||||
{
|
||||
$message = (null === $previous) ? 'Invalid JWT Token' : $previous->getMessage();
|
||||
|
||||
return new AuthenticationException($message, 401, $previous);
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
|
||||
|
||||
if (interface_exists(GuardTokenInterface::class)) {
|
||||
/**
|
||||
* Compatibility layer ensuring the guard token interface is applied when available.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class JWTCompatUserToken extends AbstractToken implements GuardTokenInterface
|
||||
{
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class JWTCompatUserToken extends AbstractToken
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JWTUserToken.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
class JWTUserToken extends JWTCompatUserToken
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $rawToken;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $providerKey;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $roles = [], UserInterface $user = null, $rawToken = null, $firewallName = null)
|
||||
{
|
||||
parent::__construct($roles);
|
||||
|
||||
if ($user) {
|
||||
$this->setUser($user);
|
||||
}
|
||||
|
||||
$this->setRawToken($rawToken);
|
||||
|
||||
if (method_exists($this, 'setAuthenticated')) {
|
||||
$this->setAuthenticated(true);
|
||||
}
|
||||
|
||||
$this->providerKey = $firewallName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $rawToken
|
||||
*/
|
||||
public function setRawToken($rawToken)
|
||||
{
|
||||
$this->rawToken = $rawToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCredentials()
|
||||
{
|
||||
return $this->rawToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 2.10, use getFirewallName() instead
|
||||
*/
|
||||
public function getProviderKey()
|
||||
{
|
||||
@trigger_error(sprintf('The "%s" method is deprecated since version 2.10 and will be removed in 3.0. Use "%s::getFirewallName()" instead.', __METHOD__, self::class), E_USER_DEPRECATED);
|
||||
|
||||
return $this->getFirewallName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFirewallName()
|
||||
{
|
||||
return $this->providerKey;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token;
|
||||
|
||||
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
|
||||
|
||||
/**
|
||||
* PreAuthenticationJWTUserToken.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class PreAuthenticationJWTUserToken extends PreAuthenticationGuardToken implements PreAuthenticationJWTUserTokenInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $rawToken;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $payload;
|
||||
|
||||
/**
|
||||
* @param string $rawToken
|
||||
*/
|
||||
public function __construct($rawToken)
|
||||
{
|
||||
$this->rawToken = $rawToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCredentials()
|
||||
{
|
||||
return $this->rawToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPayload(array $payload)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPayload()
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token;
|
||||
|
||||
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
|
||||
|
||||
interface PreAuthenticationJWTUserTokenInterface extends GuardTokenInterface
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setPayload(array $payload);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPayload();
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
|
||||
$r = new \ReflectionMethod(AuthenticatorInterface::class, 'authenticate');
|
||||
|
||||
if (!trait_exists(ForwardCompatAuthenticatorTrait::class)) {
|
||||
if ($r->hasReturnType() && Passport::class === $r->getReturnType()->getName()) {
|
||||
eval('
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait ForwardCompatAuthenticatorTrait
|
||||
{
|
||||
public function authenticate(Request $request): Passport
|
||||
{
|
||||
return $this->doAuthenticate($request);
|
||||
}
|
||||
}
|
||||
');
|
||||
} else {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait ForwardCompatAuthenticatorTrait
|
||||
{
|
||||
public function authenticate(Request $request): PassportInterface
|
||||
{
|
||||
return $this->doAuthenticate($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\ExpiredTokenException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidPayloadException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidTokenException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\MissingTokenException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\Token\JWTPostAuthenticationToken;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\PayloadAwareUserProviderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\ChainUserProvider;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class JWTAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface
|
||||
{
|
||||
use ForwardCompatAuthenticatorTrait;
|
||||
|
||||
/**
|
||||
* @var TokenExtractorInterface
|
||||
*/
|
||||
private $tokenExtractor;
|
||||
|
||||
/**
|
||||
* @var JWTTokenManagerInterface
|
||||
*/
|
||||
private $jwtManager;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
/**
|
||||
* @var UserProviderInterface
|
||||
*/
|
||||
private $userProvider;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface|null
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(
|
||||
JWTTokenManagerInterface $jwtManager,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
TokenExtractorInterface $tokenExtractor,
|
||||
UserProviderInterface $userProvider,
|
||||
TranslatorInterface $translator = null
|
||||
) {
|
||||
$this->tokenExtractor = $tokenExtractor;
|
||||
$this->jwtManager = $jwtManager;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->userProvider = $userProvider;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function start(Request $request, AuthenticationException $authException = null): Response
|
||||
{
|
||||
$exception = new MissingTokenException('JWT Token not found', 0, $authException);
|
||||
$event = new JWTNotFoundEvent($exception, new JWTAuthenticationFailureResponse($exception->getMessageKey()), $request);
|
||||
|
||||
$this->eventDispatcher->dispatch($event, Events::JWT_NOT_FOUND);
|
||||
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
public function supports(Request $request): ?bool
|
||||
{
|
||||
return false !== $this->getTokenExtractor()->extract($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Passport
|
||||
*/
|
||||
public function doAuthenticate(Request $request) /*: Passport */
|
||||
{
|
||||
$token = $this->getTokenExtractor()->extract($request);
|
||||
if ($token === false) {
|
||||
throw new \LogicException('Unable to extract a JWT token from the request. Also, make sure to call `supports()` before `authenticate()` to get a proper client error.');
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$payload = $this->jwtManager->parse($token)) {
|
||||
throw new InvalidTokenException('Invalid JWT Token');
|
||||
}
|
||||
} catch (JWTDecodeFailureException $e) {
|
||||
if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) {
|
||||
throw new ExpiredTokenException();
|
||||
}
|
||||
|
||||
throw new InvalidTokenException('Invalid JWT Token', 0, $e);
|
||||
}
|
||||
|
||||
$idClaim = $this->jwtManager->getUserIdClaim();
|
||||
if (!isset($payload[$idClaim])) {
|
||||
throw new InvalidPayloadException($idClaim);
|
||||
}
|
||||
|
||||
$passport = new SelfValidatingPassport(
|
||||
new UserBadge(
|
||||
(string)$payload[$idClaim],
|
||||
function ($userIdentifier) use ($payload) {
|
||||
return $this->loadUser($payload, $userIdentifier);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
$passport->setAttribute('payload', $payload);
|
||||
$passport->setAttribute('token', $token);
|
||||
|
||||
return $passport;
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
$errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData());
|
||||
if (null !== $this->translator) {
|
||||
$errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security');
|
||||
}
|
||||
$response = new JWTAuthenticationFailureResponse($errorMessage);
|
||||
|
||||
if ($exception instanceof ExpiredTokenException) {
|
||||
$event = new JWTExpiredEvent($exception, $response, $request);
|
||||
$eventName = Events::JWT_EXPIRED;
|
||||
} else {
|
||||
$event = new JWTInvalidEvent($exception, $response, $request);
|
||||
$eventName = Events::JWT_INVALID;
|
||||
}
|
||||
|
||||
$this->eventDispatcher->dispatch($event, $eventName);
|
||||
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token extractor to be used for retrieving a JWT token in the
|
||||
* current request.
|
||||
*
|
||||
* Override this method for adding/removing extractors to the chain one or
|
||||
* returning a different {@link TokenExtractorInterface} implementation.
|
||||
*/
|
||||
protected function getTokenExtractor(): TokenExtractorInterface
|
||||
{
|
||||
return $this->tokenExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the jwt manager.
|
||||
*/
|
||||
protected function getJwtManager(): JWTTokenManagerInterface
|
||||
{
|
||||
return $this->jwtManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the event dispatcher.
|
||||
*/
|
||||
protected function getEventDispatcher(): EventDispatcherInterface
|
||||
{
|
||||
return $this->eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user provider.
|
||||
*/
|
||||
protected function getUserProvider(): UserProviderInterface
|
||||
{
|
||||
return $this->userProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the user to authenticate.
|
||||
*
|
||||
* @param array $payload The token payload
|
||||
* @param string $identity The key from which to retrieve the user "identifier"
|
||||
*/
|
||||
protected function loadUser(array $payload, string $identity): UserInterface
|
||||
{
|
||||
if ($this->userProvider instanceof PayloadAwareUserProviderInterface) {
|
||||
if (method_exists($this->userProvider, 'loadUserByIdentifierAndPayload')) {
|
||||
return $this->userProvider->loadUserByIdentifierAndPayload($identity, $payload);
|
||||
} else {
|
||||
return $this->userProvider->loadUserByUsernameAndPayload($identity, $payload);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->userProvider instanceof ChainUserProvider) {
|
||||
foreach ($this->userProvider->getProviders() as $provider) {
|
||||
try {
|
||||
if ($provider instanceof PayloadAwareUserProviderInterface) {
|
||||
if (method_exists(PayloadAwareUserProviderInterface::class, 'loadUserByIdentifierAndPayload')) {
|
||||
return $provider->loadUserByIdentifierAndPayload($identity, $payload);
|
||||
} else {
|
||||
return $provider->loadUserByUsernameAndPayload($identity, $payload);
|
||||
}
|
||||
}
|
||||
|
||||
return $provider->loadUserByIdentifier($identity);
|
||||
// More generic call to catch both UsernameNotFoundException for SF<5.3 and new UserNotFoundException
|
||||
} catch (AuthenticationException $e) {
|
||||
// try next one
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(UserNotFoundException::class)) {
|
||||
$ex = new UsernameNotFoundException(sprintf('There is no user with username "%s".', $identity));
|
||||
$ex->setUsername($identity);
|
||||
} else {
|
||||
$ex = new UserNotFoundException(sprintf('There is no user with identifier "%s".', $identity));
|
||||
$ex->setUserIdentifier($identity);
|
||||
}
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
if (method_exists($this->userProvider, 'loadUserByIdentifier')) {
|
||||
return $this->userProvider->loadUserByIdentifier($identity);
|
||||
} else {
|
||||
return $this->userProvider->loadUserByUsername($identity);
|
||||
}
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
|
||||
{
|
||||
if (!$passport instanceof Passport) {
|
||||
throw new \LogicException(sprintf('Expected "%s" but got "%s".', Passport::class, get_debug_type($passport)));
|
||||
}
|
||||
|
||||
$token = new JWTPostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles(), $passport->getAttribute('token'));
|
||||
|
||||
$this->eventDispatcher->dispatch(new JWTAuthenticatedEvent($passport->getAttribute('payload'), $token), Events::JWT_AUTHENTICATED);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function createToken(Passport $passport, string $firewallName): TokenInterface
|
||||
{
|
||||
$token = new JWTPostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles(), $passport->getAttribute('token'));
|
||||
|
||||
$this->eventDispatcher->dispatch(new JWTAuthenticatedEvent($passport->getAttribute('payload'), $token), Events::JWT_AUTHENTICATED);
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\Token;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken;
|
||||
|
||||
class JWTPostAuthenticationToken extends PostAuthenticationToken
|
||||
{
|
||||
private $token;
|
||||
|
||||
public function __construct(UserInterface $user, string $firewallName, array $roles, string $token)
|
||||
{
|
||||
parent::__construct($user, $firewallName, $roles);
|
||||
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCredentials(): string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\JWTAuthenticator;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* JWTListener.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @deprecated since 2.0, will be removed in 3.0. See
|
||||
* {@link JWTAuthenticator} instead
|
||||
*/
|
||||
class JWTListener
|
||||
{
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
protected $tokenStorage;
|
||||
|
||||
/**
|
||||
* @var AuthenticationManagerInterface
|
||||
*/
|
||||
protected $authenticationManager;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $tokenExtractors;
|
||||
|
||||
public function __construct(
|
||||
TokenStorageInterface $tokenStorage,
|
||||
AuthenticationManagerInterface $authenticationManager,
|
||||
array $config = []
|
||||
) {
|
||||
@trigger_error(sprintf('The "%s" class is deprecated since version 2.0 and will be removed in 3.0. See "%s" instead.', self::class, JWTAuthenticator::class), E_USER_DEPRECATED);
|
||||
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->authenticationManager = $authenticationManager;
|
||||
$this->config = array_merge(['throw_exceptions' => false], $config);
|
||||
$this->tokenExtractors = [];
|
||||
}
|
||||
|
||||
public function __invoke(RequestEvent $event)
|
||||
{
|
||||
$requestToken = $this->getRequestToken($event->getRequest());
|
||||
|
||||
if (null === $requestToken) {
|
||||
$jwtNotFoundEvent = new JWTNotFoundEvent();
|
||||
$this->dispatcher->dispatch($jwtNotFoundEvent, Events::JWT_NOT_FOUND);
|
||||
|
||||
if ($response = $jwtNotFoundEvent->getResponse()) {
|
||||
$event->setResponse($response);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$token = new JWTUserToken();
|
||||
$token->setRawToken($requestToken);
|
||||
|
||||
$authToken = $this->authenticationManager->authenticate($token);
|
||||
$this->tokenStorage->setToken($authToken);
|
||||
|
||||
return;
|
||||
} catch (AuthenticationException $failed) {
|
||||
if ($this->config['throw_exceptions']) {
|
||||
throw $failed;
|
||||
}
|
||||
|
||||
$response = new JWTAuthenticationFailureResponse($failed->getMessage());
|
||||
|
||||
$jwtInvalidEvent = new JWTInvalidEvent($failed, $response, $event->getRequest());
|
||||
$this->dispatcher->dispatch($jwtInvalidEvent, Events::JWT_INVALID);
|
||||
|
||||
$event->setResponse($jwtInvalidEvent->getResponse());
|
||||
}
|
||||
}
|
||||
|
||||
public function addTokenExtractor(TokenExtractorInterface $extractor)
|
||||
{
|
||||
$this->tokenExtractors[] = $extractor;
|
||||
}
|
||||
|
||||
public function setDispatcher(EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getRequestToken(Request $request)
|
||||
{
|
||||
/** @var TokenExtractorInterface $tokenExtractor */
|
||||
foreach ($this->tokenExtractors as $tokenExtractor) {
|
||||
if (($token = $tokenExtractor->extract($request))) {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Guard;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\ExpiredTokenException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidPayloadException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidTokenException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\MissingTokenException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\UserNotFoundException;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\PreAuthenticationJWTUserToken;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\PreAuthenticationJWTUserTokenInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\PayloadAwareUserProviderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException as SecurityUserNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\ChainUserProvider;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Guard\AuthenticatorInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* JWTTokenAuthenticator (Guard implementation).
|
||||
*
|
||||
* @see http://knpuniversity.com/screencast/symfony-rest4/jwt-guard-authenticator
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTTokenAuthenticator implements AuthenticatorInterface
|
||||
{
|
||||
/**
|
||||
* @var JWTTokenManagerInterface
|
||||
*/
|
||||
private $jwtManager;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
private $dispatcher;
|
||||
|
||||
/**
|
||||
* @var TokenExtractorInterface
|
||||
*/
|
||||
private $tokenExtractor;
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
private $preAuthenticationTokenStorage;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(
|
||||
JWTTokenManagerInterface $jwtManager,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
TokenExtractorInterface $tokenExtractor,
|
||||
TokenStorageInterface $preAuthenticationTokenStorage,
|
||||
TranslatorInterface $translator = null
|
||||
) {
|
||||
$this->jwtManager = $jwtManager;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->tokenExtractor = $tokenExtractor;
|
||||
$this->preAuthenticationTokenStorage = $preAuthenticationTokenStorage;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function supports(Request $request)
|
||||
{
|
||||
return false !== $this->getTokenExtractor()->extract($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a decoded JWT token extracted from a request.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return PreAuthenticationJWTUserTokenInterface
|
||||
*
|
||||
* @throws InvalidTokenException If an error occur while decoding the token
|
||||
* @throws ExpiredTokenException If the request token is expired
|
||||
*/
|
||||
public function getCredentials(Request $request)
|
||||
{
|
||||
$tokenExtractor = $this->getTokenExtractor();
|
||||
|
||||
if (!$tokenExtractor instanceof TokenExtractorInterface) {
|
||||
throw new \RuntimeException(sprintf('Method "%s::getTokenExtractor()" must return an instance of "%s".', self::class, TokenExtractorInterface::class));
|
||||
}
|
||||
|
||||
if (false === ($jsonWebToken = $tokenExtractor->extract($request))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$preAuthToken = new PreAuthenticationJWTUserToken($jsonWebToken);
|
||||
|
||||
try {
|
||||
if (!$payload = $this->jwtManager->decode($preAuthToken)) {
|
||||
throw new InvalidTokenException('Invalid JWT Token');
|
||||
}
|
||||
|
||||
$preAuthToken->setPayload($payload);
|
||||
} catch (JWTDecodeFailureException $e) {
|
||||
if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) {
|
||||
$expiredTokenException = new ExpiredTokenException();
|
||||
$expiredTokenException->setToken($preAuthToken);
|
||||
throw $expiredTokenException;
|
||||
}
|
||||
|
||||
throw new InvalidTokenException('Invalid JWT Token', 0, $e);
|
||||
}
|
||||
|
||||
return $preAuthToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an user object loaded from a JWT token.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param PreAuthenticationJWTUserTokenInterface $preAuthToken Implementation of the (Security) TokenInterface
|
||||
*
|
||||
* @throws \InvalidArgumentException If preAuthToken is not of the good type
|
||||
* @throws InvalidPayloadException If the user identity field is not a key of the payload
|
||||
* @throws UserNotFoundException If no user can be loaded from the given token
|
||||
*/
|
||||
public function getUser($preAuthToken, UserProviderInterface $userProvider)
|
||||
{
|
||||
if (!$preAuthToken instanceof PreAuthenticationJWTUserTokenInterface) {
|
||||
throw new \InvalidArgumentException(sprintf('The first argument of the "%s()" method must be an instance of "%s".', __METHOD__, PreAuthenticationJWTUserTokenInterface::class));
|
||||
}
|
||||
|
||||
$payload = $preAuthToken->getPayload();
|
||||
$idClaim = $this->jwtManager->getUserIdClaim();
|
||||
|
||||
if (!isset($payload[$idClaim])) {
|
||||
throw new InvalidPayloadException($idClaim);
|
||||
}
|
||||
|
||||
$user = $this->loadUser($userProvider, $payload, $payload[$idClaim]);
|
||||
|
||||
$this->preAuthenticationTokenStorage->setToken($preAuthToken);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $authException)
|
||||
{
|
||||
$errorMessage = strtr($authException->getMessageKey(), $authException->getMessageData());
|
||||
if (null !== $this->translator) {
|
||||
$errorMessage = $this->translator->trans($authException->getMessageKey(), $authException->getMessageData(), 'security');
|
||||
}
|
||||
$response = new JWTAuthenticationFailureResponse($errorMessage);
|
||||
|
||||
if ($authException instanceof ExpiredTokenException) {
|
||||
$event = new JWTExpiredEvent($authException, $response, $request);
|
||||
$eventName = Events::JWT_EXPIRED;
|
||||
} else {
|
||||
$event = new JWTInvalidEvent($authException, $response, $request);
|
||||
$eventName = Events::JWT_INVALID;
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch($event, $eventName);
|
||||
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return JWTAuthenticationFailureResponse
|
||||
*/
|
||||
public function start(Request $request, AuthenticationException $authException = null)
|
||||
{
|
||||
$exception = new MissingTokenException('JWT Token not found', 0, $authException);
|
||||
$event = new JWTNotFoundEvent($exception, new JWTAuthenticationFailureResponse($exception->getMessageKey()), $request);
|
||||
|
||||
$this->dispatcher->dispatch($event, Events::JWT_NOT_FOUND);
|
||||
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkCredentials($credentials, UserInterface $user)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \RuntimeException If there is no pre-authenticated token previously stored
|
||||
*/
|
||||
public function createAuthenticatedToken(UserInterface $user, $providerKey)
|
||||
{
|
||||
$preAuthToken = $this->preAuthenticationTokenStorage->getToken();
|
||||
|
||||
if (null === $preAuthToken) {
|
||||
throw new \RuntimeException('Unable to return an authenticated token since there is no pre authentication token.');
|
||||
}
|
||||
|
||||
$authToken = new JWTUserToken($user->getRoles(), $user, $preAuthToken->getCredentials(), $providerKey);
|
||||
|
||||
$this->dispatcher->dispatch(new JWTAuthenticatedEvent($preAuthToken->getPayload(), $authToken), Events::JWT_AUTHENTICATED);
|
||||
|
||||
$this->preAuthenticationTokenStorage->setToken(null);
|
||||
|
||||
return $authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsRememberMe()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token extractor to be used for retrieving a JWT token in the
|
||||
* current request.
|
||||
*
|
||||
* Override this method for adding/removing extractors to the chain one or
|
||||
* returning a different {@link TokenExtractorInterface} implementation.
|
||||
*
|
||||
* @return TokenExtractorInterface
|
||||
*/
|
||||
protected function getTokenExtractor()
|
||||
{
|
||||
return $this->tokenExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JWTTokenManagerInterface
|
||||
*/
|
||||
protected function getJwtManager()
|
||||
{
|
||||
return $this->jwtManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EventDispatcherInterface
|
||||
*/
|
||||
protected function getDispatcher()
|
||||
{
|
||||
return $this->dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenStorageInterface
|
||||
*/
|
||||
protected function getPreAuthenticationTokenStorage()
|
||||
{
|
||||
return $this->preAuthenticationTokenStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the user to authenticate.
|
||||
*
|
||||
* @param UserProviderInterface $userProvider An user provider
|
||||
* @param array $payload The token payload
|
||||
* @param string $identity The key from which to retrieve the user "username"
|
||||
*
|
||||
* @return UserInterface
|
||||
*/
|
||||
protected function loadUser(UserProviderInterface $userProvider, array $payload, $identity)
|
||||
{
|
||||
$providers = $userProvider instanceof ChainUserProvider ? $userProvider->getProviders() : [$userProvider];
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
try {
|
||||
if ($provider instanceof PayloadAwareUserProviderInterface) {
|
||||
return $provider->loadUserByUsernameAndPayload($identity, $payload);
|
||||
}
|
||||
|
||||
if (method_exists($provider, 'loadUserByIdentifier')) {
|
||||
return $provider->loadUserByIdentifier($identity);
|
||||
}
|
||||
|
||||
return $provider->loadUserByUsername($identity);
|
||||
} catch (SecurityUserNotFoundException | UsernameNotFoundException $e) {
|
||||
// try next one
|
||||
}
|
||||
}
|
||||
|
||||
if (class_exists(SecurityUserNotFoundException::class)) {
|
||||
$ex = new SecurityUserNotFoundException(sprintf('There is no user with name "%s".', $identity));
|
||||
$ex->setUserIdentifier($identity);
|
||||
} else {
|
||||
$ex = new UsernameNotFoundException(sprintf('There is no user with name "%s".', $identity));
|
||||
$ex->setUsername($identity);
|
||||
}
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
vendor/lexik/jwt-authentication-bundle/Security/Http/Authentication/AuthenticationFailureHandler.php
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* AuthenticationFailureHandler.
|
||||
*
|
||||
* @author Dev Lexik <dev@lexik.fr>
|
||||
*/
|
||||
class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface|null
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(EventDispatcherInterface $dispatcher, TranslatorInterface $translator = null)
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
|
||||
{
|
||||
$errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData());
|
||||
$statusCode = self::mapExceptionCodeToStatusCode($exception->getCode());
|
||||
if ($this->translator) {
|
||||
$errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security');
|
||||
}
|
||||
|
||||
$event = new AuthenticationFailureEvent(
|
||||
$exception,
|
||||
new JWTAuthenticationFailureResponse($errorMessage, $statusCode),
|
||||
$request
|
||||
);
|
||||
|
||||
$this->dispatcher->dispatch($event, Events::AUTHENTICATION_FAILURE);
|
||||
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $exceptionCode
|
||||
*/
|
||||
private static function mapExceptionCodeToStatusCode($exceptionCode): int
|
||||
{
|
||||
$canMapToStatusCode = is_int($exceptionCode)
|
||||
&& $exceptionCode >= 400
|
||||
&& $exceptionCode < 500;
|
||||
|
||||
return $canMapToStatusCode
|
||||
? $exceptionCode
|
||||
: Response::HTTP_UNAUTHORIZED;
|
||||
}
|
||||
}
|
||||
vendor/lexik/jwt-authentication-bundle/Security/Http/Authentication/AuthenticationSuccessHandler.php
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationSuccessResponse;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Cookie\JWTCookieProvider;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* AuthenticationSuccessHandler.
|
||||
*
|
||||
* @author Dev Lexik <dev@lexik.fr>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface
|
||||
{
|
||||
private $cookieProviders;
|
||||
|
||||
protected $jwtManager;
|
||||
protected $dispatcher;
|
||||
protected $removeTokenFromBodyWhenCookiesUsed;
|
||||
|
||||
/**
|
||||
* @param iterable|JWTCookieProvider[] $cookieProviders
|
||||
*/
|
||||
public function __construct(JWTTokenManagerInterface $jwtManager, EventDispatcherInterface $dispatcher, $cookieProviders = [], bool $removeTokenFromBodyWhenCookiesUsed = true)
|
||||
{
|
||||
$this->jwtManager = $jwtManager;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->cookieProviders = $cookieProviders;
|
||||
$this->removeTokenFromBodyWhenCookiesUsed = $removeTokenFromBodyWhenCookiesUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response
|
||||
{
|
||||
return $this->handleAuthenticationSuccess($token->getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public function handleAuthenticationSuccess(UserInterface $user, $jwt = null)
|
||||
{
|
||||
if (null === $jwt) {
|
||||
$jwt = $this->jwtManager->create($user);
|
||||
}
|
||||
|
||||
$jwtCookies = [];
|
||||
foreach ($this->cookieProviders as $cookieProvider) {
|
||||
$jwtCookies[] = $cookieProvider->createCookie($jwt);
|
||||
}
|
||||
|
||||
$response = new JWTAuthenticationSuccessResponse($jwt, [], $jwtCookies);
|
||||
$event = new AuthenticationSuccessEvent(['token' => $jwt], $user, $response);
|
||||
|
||||
$this->dispatcher->dispatch($event, Events::AUTHENTICATION_SUCCESS);
|
||||
$responseData = $event->getData();
|
||||
|
||||
if ($jwtCookies && $this->removeTokenFromBodyWhenCookiesUsed) {
|
||||
unset($responseData['token']);
|
||||
}
|
||||
|
||||
if ($responseData) {
|
||||
$response->setData($responseData);
|
||||
} else {
|
||||
$response->setStatusCode(JWTAuthenticationSuccessResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Cookie;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Helper\JWTSplitter;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
|
||||
/**
|
||||
* Creates secure JWT cookies.
|
||||
*/
|
||||
final class JWTCookieProvider
|
||||
{
|
||||
private $defaultName;
|
||||
private $defaultLifetime;
|
||||
private $defaultSameSite;
|
||||
private $defaultPath;
|
||||
private $defaultDomain;
|
||||
private $defaultSecure;
|
||||
private $defaultHttpOnly;
|
||||
private $defaultSplit;
|
||||
private $defaultPartitioned;
|
||||
|
||||
public function __construct(?string $defaultName = null, ?int $defaultLifetime = 0, ?string $defaultSameSite = Cookie::SAMESITE_LAX, ?string $defaultPath = '/', ?string $defaultDomain = null, bool $defaultSecure = true, bool $defaultHttpOnly = true, array $defaultSplit = [], bool $defaultPartitioned = false)
|
||||
{
|
||||
$this->defaultName = $defaultName;
|
||||
$this->defaultLifetime = $defaultLifetime;
|
||||
$this->defaultSameSite = $defaultSameSite;
|
||||
$this->defaultPath = $defaultPath;
|
||||
$this->defaultDomain = $defaultDomain;
|
||||
$this->defaultSecure = $defaultSecure;
|
||||
$this->defaultHttpOnly = $defaultHttpOnly;
|
||||
$this->defaultSplit = $defaultSplit;
|
||||
$this->defaultPartitioned = $defaultPartitioned;
|
||||
|
||||
if ($defaultPartitioned && Kernel::VERSION < '6.4') {
|
||||
throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a secure cookie containing the passed JWT.
|
||||
*
|
||||
* For each argument (all args except $jwt), if omitted or set to null then the
|
||||
* default value defined via the constructor will be used.
|
||||
*/
|
||||
public function createCookie(string $jwt, ?string $name = null, $expiresAt = null, ?string $sameSite = null, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, array $split = [], ?bool $partitioned = null): Cookie
|
||||
{
|
||||
if (!$name && !$this->defaultName) {
|
||||
throw new \LogicException(sprintf('The cookie name must be provided, either pass it as 2nd argument of %s or set a default name via the constructor.', __METHOD__));
|
||||
}
|
||||
|
||||
if (!$expiresAt && null === $this->defaultLifetime) {
|
||||
throw new \LogicException(sprintf('The cookie expiration time must be provided, either pass it as 3rd argument of %s or set a default lifetime via the constructor.', __METHOD__));
|
||||
}
|
||||
|
||||
if ($partitioned && Kernel::VERSION < '6.4') {
|
||||
throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION));
|
||||
}
|
||||
|
||||
$jwtParts = new JWTSplitter($jwt);
|
||||
$jwt = $jwtParts->getParts($split ?: $this->defaultSplit);
|
||||
|
||||
if (null === $expiresAt) {
|
||||
$expiresAt = 0 === $this->defaultLifetime ? 0 : (time() + $this->defaultLifetime);
|
||||
}
|
||||
|
||||
return Cookie::create(
|
||||
$name ?: $this->defaultName,
|
||||
$jwt,
|
||||
$expiresAt,
|
||||
$path ?: $this->defaultPath,
|
||||
$domain ?: $this->defaultDomain,
|
||||
$secure ?: $this->defaultSecure,
|
||||
$httpOnly ?: $this->defaultHttpOnly,
|
||||
false,
|
||||
$sameSite ?: $this->defaultSameSite,
|
||||
$partitioned ?: $this->defaultPartitioned
|
||||
);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Http\EntryPoint;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\JWTAuthenticator;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
|
||||
/**
|
||||
* JWTEntryPoint starts throw a 401 when not authenticated.
|
||||
*
|
||||
* @author Jérémie Augustin <jeremie.augustin@pixel-cookers.com>
|
||||
*
|
||||
* @deprecated since 2.0, will be removed in 3.0. Use
|
||||
* {@link JWTAuthenticator} instead
|
||||
*/
|
||||
class JWTEntryPoint implements AuthenticationEntryPointInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
@trigger_error(sprintf('The "%s" class is deprecated since version 2.0 and will be removed in 3.0. Use "%s" instead.', self::class, JWTAuthenticator::class), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function start(Request $request, AuthenticationException $authException = null): Response
|
||||
{
|
||||
$response = new JWTAuthenticationFailureResponse();
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User;
|
||||
|
||||
/**
|
||||
* User class for which to create instances from JWT tokens.
|
||||
*
|
||||
* Note: This is only useful when using the JWTUserProvider (database-less).
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTUser implements JWTUserInterface
|
||||
{
|
||||
private $userIdentifier;
|
||||
private $roles;
|
||||
|
||||
public function __construct(string $userIdentifier, array $roles = [])
|
||||
{
|
||||
$this->userIdentifier = $userIdentifier;
|
||||
$this->roles = $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createFromPayload($username, array $payload)
|
||||
{
|
||||
if (isset($payload['roles'])) {
|
||||
return new static($username, (array) $payload['roles']);
|
||||
}
|
||||
|
||||
return new static($username);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->getUserIdentifier();
|
||||
}
|
||||
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return $this->userIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoles(): array
|
||||
{
|
||||
return $this->roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSalt(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
interface JWTUserInterface extends UserInterface
|
||||
{
|
||||
/**
|
||||
* Creates a new instance from a given JWT payload.
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return JWTUserInterface
|
||||
*/
|
||||
public static function createFromPayload($username, array $payload);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* JWT User provider.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class JWTUserProvider implements PayloadAwareUserProviderInterface
|
||||
{
|
||||
private $class;
|
||||
|
||||
private $cache = [];
|
||||
|
||||
/**
|
||||
* @param string $class The {@link JWTUserInterface} implementation FQCN for which to provide instances
|
||||
*/
|
||||
public function __construct($class)
|
||||
{
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $payload The JWT payload from which to create an instance
|
||||
*
|
||||
* @return UserInterface
|
||||
*/
|
||||
public function loadUserByUsername($username, array $payload = [])
|
||||
{
|
||||
return $this->loadUserByUsernameAndPayload($username, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $payload The JWT payload from which to create an instance
|
||||
*/
|
||||
public function loadUserByIdentifier(string $identifier, array $payload = []): UserInterface
|
||||
{
|
||||
return $this->loadUserByIdentifierAndPayload($identifier, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadUserByUsernameAndPayload(string $username, array $payload): UserInterface
|
||||
{
|
||||
if (isset($this->cache[$username])) {
|
||||
return $this->cache[$username];
|
||||
}
|
||||
|
||||
$class = $this->class;
|
||||
|
||||
return $this->cache[$username] = $class::createFromPayload($username, $payload);
|
||||
}
|
||||
|
||||
public function loadUserByIdentifierAndPayload(string $userIdentifier, array $payload): UserInterface
|
||||
{
|
||||
if (isset($this->cache[$userIdentifier])) {
|
||||
return $this->cache[$userIdentifier];
|
||||
}
|
||||
|
||||
$class = $this->class;
|
||||
|
||||
return $this->cache[$userIdentifier] = $class::createFromPayload($userIdentifier, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsClass($class): bool
|
||||
{
|
||||
return $class === $this->class || (new \ReflectionClass($class))->implementsInterface(JWTUserInterface::class);
|
||||
}
|
||||
|
||||
public function refreshUser(UserInterface $user): UserInterface
|
||||
{
|
||||
return $user; // noop
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\UserNotFoundException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
/**
|
||||
* @method UserInterface loadUserByIdentifierAndPayload(string $identifier, array $payload) Loads a user from an identifier and JWT token payload.
|
||||
*/
|
||||
interface PayloadAwareUserProviderInterface extends UserProviderInterface
|
||||
{
|
||||
/**
|
||||
* Load a user by its username, including the JWT token payload.
|
||||
*
|
||||
* @throws UsernameNotFoundException|UserNotFoundException if the user is not found
|
||||
*
|
||||
* @deprecated since 2.12, implement loadUserByIdentifierAndPayload() instead.
|
||||
*/
|
||||
public function loadUserByUsernameAndPayload(string $username, array $payload)/*: UserInterface*/;
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider;
|
||||
|
||||
@trigger_error(sprintf('The "%s" class is deprecated since version 2.5 and will be removed in 3.0. Use "%s" or create your own "%s" implementation instead.', DefaultJWSProvider::class, LcobucciJWSProvider::class, JWSProviderInterface::class), E_USER_DEPRECATED);
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\KeyLoaderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\CreatedJWS;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\LoadedJWS;
|
||||
use Namshi\JOSE\JWS;
|
||||
|
||||
/**
|
||||
* JWS Provider, Namshi\JOSE library integration.
|
||||
* Supports OpenSSL and phpseclib crypto engines.
|
||||
*
|
||||
* @final
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @deprecated since version 2.5, to be removed in 3.0
|
||||
*/
|
||||
class DefaultJWSProvider implements JWSProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var KeyLoaderInterface
|
||||
*/
|
||||
private $keyLoader;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cryptoEngine;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $signatureAlgorithm;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $ttl;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $clockSkew;
|
||||
|
||||
/**
|
||||
* @param string $cryptoEngine
|
||||
* @param string $signatureAlgorithm
|
||||
* @param int $ttl
|
||||
* @param int $clockSkew
|
||||
*
|
||||
* @throws \InvalidArgumentException If the given algorithm is not supported
|
||||
*/
|
||||
public function __construct(KeyLoaderInterface $keyLoader, $cryptoEngine, $signatureAlgorithm, $ttl, $clockSkew)
|
||||
{
|
||||
if (null !== $ttl && !is_numeric($ttl)) {
|
||||
throw new \InvalidArgumentException(sprintf('The TTL should be a numeric value, got %s instead.', $ttl));
|
||||
}
|
||||
|
||||
if (null !== $clockSkew && !is_numeric($clockSkew)) {
|
||||
throw new \InvalidArgumentException(sprintf('The clock skew should be a numeric value, got %s instead.', $clockSkew));
|
||||
}
|
||||
|
||||
$cryptoEngine = 'openssl' == $cryptoEngine ? 'OpenSSL' : 'SecLib';
|
||||
|
||||
if (!$this->isAlgorithmSupportedForEngine($cryptoEngine, $signatureAlgorithm)) {
|
||||
throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported for %s', $signatureAlgorithm, $cryptoEngine));
|
||||
}
|
||||
|
||||
$this->keyLoader = $keyLoader;
|
||||
$this->cryptoEngine = $cryptoEngine;
|
||||
$this->signatureAlgorithm = $signatureAlgorithm;
|
||||
$this->ttl = $ttl;
|
||||
$this->clockSkew = $clockSkew;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(array $payload, array $header = [])
|
||||
{
|
||||
$header['alg'] = $this->signatureAlgorithm;
|
||||
|
||||
$jws = new JWS($header, $this->cryptoEngine);
|
||||
$claims = ['iat' => time()];
|
||||
|
||||
if (null !== $this->ttl && !isset($payload['exp'])) {
|
||||
$claims['exp'] = time() + $this->ttl;
|
||||
}
|
||||
|
||||
$jws->setPayload($payload + $claims);
|
||||
$jws->sign(
|
||||
$this->keyLoader->loadKey('private'),
|
||||
$this->keyLoader->getPassphrase()
|
||||
);
|
||||
|
||||
return new CreatedJWS($jws->getTokenString(), $jws->isSigned());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($token)
|
||||
{
|
||||
$jws = JWS::load($token, false, null, $this->cryptoEngine);
|
||||
|
||||
return new LoadedJWS(
|
||||
$jws->getPayload(),
|
||||
$jws->verify($this->keyLoader->loadKey('public'), $this->signatureAlgorithm),
|
||||
null !== $this->ttl,
|
||||
$jws->getHeader(),
|
||||
$this->clockSkew
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cryptoEngine
|
||||
* @param string $signatureAlgorithm
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isAlgorithmSupportedForEngine($cryptoEngine, $signatureAlgorithm)
|
||||
{
|
||||
$signerClass = sprintf('Namshi\\JOSE\\Signer\\%s\\%s', $cryptoEngine, $signatureAlgorithm);
|
||||
|
||||
return class_exists($signerClass);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\CreatedJWS;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\LoadedJWS;
|
||||
|
||||
/**
|
||||
* Interface for classes that are able to create and load JSON web signatures (JWS).
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
interface JWSProviderInterface
|
||||
{
|
||||
/**
|
||||
* Creates a new JWS signature from a given payload.
|
||||
*
|
||||
* @return CreatedJWS
|
||||
*/
|
||||
public function create(array $payload, array $header = []);
|
||||
|
||||
/**
|
||||
* Loads an existing JWS signature from a given JWT token.
|
||||
*
|
||||
* @param string $token
|
||||
*
|
||||
* @return LoadedJWS
|
||||
*/
|
||||
public function load($token);
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider;
|
||||
|
||||
use Lcobucci\Clock\Clock;
|
||||
use Lcobucci\Clock\SystemClock;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use Lcobucci\JWT\Encoding\ChainedFormatter;
|
||||
use Lcobucci\JWT\Encoding\JoseEncoder;
|
||||
use Lcobucci\JWT\Parser;
|
||||
use Lcobucci\JWT\Signer;
|
||||
use Lcobucci\JWT\Signer\Ecdsa;
|
||||
use Lcobucci\JWT\Signer\Hmac;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha384;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha512;
|
||||
use Lcobucci\JWT\Signer\Key;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\Token\Builder as JWTBuilder;
|
||||
use Lcobucci\JWT\Token\Parser as JWTParser;
|
||||
use Lcobucci\JWT\Token\Plain;
|
||||
use Lcobucci\JWT\Token\RegisteredClaims;
|
||||
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
|
||||
use Lcobucci\JWT\Validation\Constraint\SignedWith;
|
||||
use Lcobucci\JWT\Validation\Constraint\ValidAt;
|
||||
use Lcobucci\JWT\Validation\Validator;
|
||||
use Lcobucci\JWT\ValidationData;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\KeyLoaderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader\RawKeyLoader;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\CreatedJWS;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Signature\LoadedJWS;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class LcobucciJWSProvider implements JWSProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var KeyLoaderInterface
|
||||
*/
|
||||
private $keyLoader;
|
||||
|
||||
/**
|
||||
* @var Clock
|
||||
*/
|
||||
private $clock;
|
||||
|
||||
/**
|
||||
* @var Signer
|
||||
*/
|
||||
private $signer;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $ttl;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $clockSkew;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $allowNoExpiration;
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException If the given crypto engine is not supported
|
||||
*/
|
||||
public function __construct(KeyLoaderInterface $keyLoader, string $cryptoEngine, string $signatureAlgorithm, ?int $ttl, ?int $clockSkew, bool $allowNoExpiration = false, Clock $clock = null)
|
||||
{
|
||||
if ('openssl' !== $cryptoEngine) {
|
||||
throw new \InvalidArgumentException(sprintf('The %s provider supports only "openssl" as crypto engine.', self::class));
|
||||
}
|
||||
if (null === $clock) {
|
||||
$clock = new SystemClock(new \DateTimeZone('UTC'));
|
||||
}
|
||||
|
||||
$this->keyLoader = $keyLoader;
|
||||
$this->clock = $clock;
|
||||
$this->signer = $this->getSignerForAlgorithm($signatureAlgorithm);
|
||||
$this->ttl = $ttl;
|
||||
$this->clockSkew = $clockSkew;
|
||||
$this->allowNoExpiration = $allowNoExpiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(array $payload, array $header = [])
|
||||
{
|
||||
if (class_exists(JWTBuilder::class)) {
|
||||
$jws = new JWTBuilder(new JoseEncoder(), ChainedFormatter::default());
|
||||
} else {
|
||||
$jws = new Builder();
|
||||
}
|
||||
|
||||
foreach ($header as $k => $v) {
|
||||
$jws = $jws->withHeader($k, $v);
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
$issuedAt = $payload['iat'] ?? $now;
|
||||
unset($payload['iat']);
|
||||
|
||||
$jws = $jws->issuedAt(!$issuedAt instanceof \DateTimeImmutable ? new \DateTimeImmutable("@{$issuedAt}") : $issuedAt);
|
||||
|
||||
if (null !== $this->ttl || isset($payload['exp'])) {
|
||||
$exp = $payload['exp'] ?? $now + $this->ttl;
|
||||
unset($payload['exp']);
|
||||
|
||||
if ($exp) {
|
||||
$jws = $jws->expiresAt(!$exp instanceof \DateTimeImmutable ? new \DateTimeImmutable("@{$exp}") : $exp);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($payload['sub'])) {
|
||||
$jws = $jws->relatedTo($payload['sub']);
|
||||
unset($payload['sub']);
|
||||
}
|
||||
|
||||
if (interface_exists(RegisteredClaims::class)) {
|
||||
$jws = $this->addStandardClaims($jws, $payload);
|
||||
}
|
||||
|
||||
foreach ($payload as $name => $value) {
|
||||
$jws = $jws->withClaim($name, $value);
|
||||
}
|
||||
|
||||
$e = $token = null;
|
||||
try {
|
||||
$token = $this->getSignedToken($jws);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
|
||||
return new CreatedJWS((string) $token, null === $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($token)
|
||||
{
|
||||
if (class_exists(JWTParser::class)) {
|
||||
$jws = (new JWTParser(new JoseEncoder()))->parse((string) $token);
|
||||
} else {
|
||||
$jws = (new Parser())->parse((string) $token);
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
foreach ($jws->claims()->all() as $name => $value) {
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
$value = $value->getTimestamp();
|
||||
}
|
||||
$payload[$name] = $value;
|
||||
}
|
||||
|
||||
$jws = new LoadedJWS(
|
||||
$payload,
|
||||
$this->verify($jws),
|
||||
false == $this->allowNoExpiration,
|
||||
$jws->headers()->all(),
|
||||
$this->clockSkew
|
||||
);
|
||||
|
||||
return $jws;
|
||||
}
|
||||
|
||||
private function getSignerForAlgorithm($signatureAlgorithm)
|
||||
{
|
||||
$signerMap = [
|
||||
'HS256' => Sha256::class,
|
||||
'HS384' => Sha384::class,
|
||||
'HS512' => Sha512::class,
|
||||
'RS256' => Signer\Rsa\Sha256::class,
|
||||
'RS384' => Signer\Rsa\Sha384::class,
|
||||
'RS512' => Signer\Rsa\Sha512::class,
|
||||
'ES256' => Signer\Ecdsa\Sha256::class,
|
||||
'ES384' => Signer\Ecdsa\Sha384::class,
|
||||
'ES512' => Signer\Ecdsa\Sha512::class,
|
||||
];
|
||||
|
||||
if (!isset($signerMap[$signatureAlgorithm])) {
|
||||
throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported by %s', $signatureAlgorithm, self::class));
|
||||
}
|
||||
|
||||
$signerClass = $signerMap[$signatureAlgorithm];
|
||||
|
||||
if (is_subclass_of($signerClass, Ecdsa::class) && method_exists($signerClass, 'create')) {
|
||||
return $signerClass::create();
|
||||
}
|
||||
|
||||
return new $signerClass();
|
||||
}
|
||||
|
||||
private function getSignedToken(Builder $jws)
|
||||
{
|
||||
if (class_exists(InMemory::class)) {
|
||||
$key = InMemory::plainText($this->keyLoader->loadKey(RawKeyLoader::TYPE_PRIVATE), $this->signer instanceof Hmac ? '' : (string) $this->keyLoader->getPassphrase());
|
||||
} else {
|
||||
$key = new Key($this->keyLoader->loadKey(RawKeyLoader::TYPE_PRIVATE), $this->signer instanceof Hmac ? '' : (string) $this->keyLoader->getPassphrase());
|
||||
}
|
||||
|
||||
$token = $jws->getToken($this->signer, $key);
|
||||
|
||||
if (!$token instanceof Plain) {
|
||||
return (string) $token;
|
||||
}
|
||||
|
||||
return $token->toString();
|
||||
}
|
||||
|
||||
private function verify(Token $jwt)
|
||||
{
|
||||
if (class_exists(InMemory::class)) {
|
||||
$key = InMemory::plainText($this->signer instanceof Hmac ? $this->keyLoader->loadKey(RawKeyLoader::TYPE_PRIVATE) : $this->keyLoader->loadKey(RawKeyLoader::TYPE_PUBLIC));
|
||||
} else {
|
||||
$key = new Key($this->signer instanceof Hmac ? $this->keyLoader->loadKey(RawKeyLoader::TYPE_PRIVATE) : $this->keyLoader->loadKey(RawKeyLoader::TYPE_PUBLIC));
|
||||
}
|
||||
|
||||
$validator = new Validator();
|
||||
$classValidator = class_exists(LooseValidAt::class) ? LooseValidAt::class : ValidAt::class;
|
||||
|
||||
$isValid = $validator->validate(
|
||||
$jwt,
|
||||
new $classValidator($this->clock, new \DateInterval("PT{$this->clockSkew}S")),
|
||||
new SignedWith($this->signer, $key)
|
||||
);
|
||||
|
||||
$publicKeys = $this->keyLoader->getAdditionalPublicKeys();
|
||||
if ($isValid || $this->signer instanceof Hmac || empty($publicKeys)) {
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
// If the key used to verify the token is invalid, and it's not Hmac algorithm, try with additional public keys
|
||||
foreach ($publicKeys as $key) {
|
||||
$isValid = $validator->validate(
|
||||
$jwt,
|
||||
new $classValidator($this->clock, new \DateInterval("PT{$this->clockSkew}S")),
|
||||
new SignedWith($this->signer, InMemory::plainText($key))
|
||||
);
|
||||
|
||||
if ($isValid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function addStandardClaims(Builder $builder, array &$payload): Builder
|
||||
{
|
||||
$mutatorMap = [
|
||||
RegisteredClaims::AUDIENCE => 'permittedFor',
|
||||
RegisteredClaims::ID => 'identifiedBy',
|
||||
RegisteredClaims::ISSUER => 'issuedBy',
|
||||
RegisteredClaims::NOT_BEFORE => 'canOnlyBeUsedAfter',
|
||||
];
|
||||
|
||||
foreach ($payload as $claim => $value) {
|
||||
if (!isset($mutatorMap[$claim])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mutator = $mutatorMap[$claim];
|
||||
unset($payload[$claim]);
|
||||
|
||||
if (\is_array($value)) {
|
||||
$builder = \call_user_func_array([$builder, $mutator], $value);
|
||||
continue;
|
||||
}
|
||||
|
||||
$builder = $builder->{$mutator}($value);
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\HeaderAwareJWTEncoderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTEncodedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\InMemoryUser;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Provides convenient methods to manage JWT creation/verification.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class JWTManager implements JWTManagerInterface, JWTTokenManagerInterface
|
||||
{
|
||||
/**
|
||||
* @var JWTEncoderInterface
|
||||
*/
|
||||
protected $jwtEncoder;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @deprecated since v2.15
|
||||
*/
|
||||
protected $userIdentityField;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $userIdClaim;
|
||||
|
||||
/**
|
||||
* @param string|null $userIdClaim
|
||||
*/
|
||||
public function __construct(JWTEncoderInterface $encoder, EventDispatcherInterface $dispatcher, $userIdClaim = null)
|
||||
{
|
||||
$this->jwtEncoder = $encoder;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->userIdentityField = 'username';
|
||||
$this->userIdClaim = $userIdClaim;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
public function create(UserInterface $user): string
|
||||
{
|
||||
$payload = ['roles' => $user->getRoles()];
|
||||
$this->addUserIdentityToPayload($user, $payload);
|
||||
|
||||
return $this->generateJwtStringAndDispatchEvents($user, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
public function createFromPayload(UserInterface $user, array $payload): string
|
||||
{
|
||||
$payload = array_merge(['roles' => $user->getRoles()], $payload);
|
||||
$this->addUserIdentityToPayload($user, $payload);
|
||||
|
||||
return $this->generateJwtStringAndDispatchEvents($user, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
private function generateJwtStringAndDispatchEvents(UserInterface $user, array $payload): string
|
||||
{
|
||||
$jwtCreatedEvent = new JWTCreatedEvent($payload, $user);
|
||||
$this->dispatcher->dispatch($jwtCreatedEvent, Events::JWT_CREATED);
|
||||
|
||||
if ($this->jwtEncoder instanceof HeaderAwareJWTEncoderInterface) {
|
||||
$jwtString = $this->jwtEncoder->encode($jwtCreatedEvent->getData(), $jwtCreatedEvent->getHeader());
|
||||
} else {
|
||||
$jwtString = $this->jwtEncoder->encode($jwtCreatedEvent->getData());
|
||||
}
|
||||
|
||||
$jwtEncodedEvent = new JWTEncodedEvent($jwtString);
|
||||
|
||||
$this->dispatcher->dispatch($jwtEncodedEvent, Events::JWT_ENCODED);
|
||||
|
||||
return $jwtString;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws JWTDecodeFailureException
|
||||
*/
|
||||
public function decode(TokenInterface $token)
|
||||
{
|
||||
if (!($payload = $this->jwtEncoder->decode($token->getCredentials()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$event = new JWTDecodedEvent($payload);
|
||||
$this->dispatcher->dispatch($event, Events::JWT_DECODED);
|
||||
|
||||
if (!$event->isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $event->getPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(string $jwtToken): array
|
||||
{
|
||||
$payload = $this->jwtEncoder->decode($jwtToken);
|
||||
|
||||
$event = new JWTDecodedEvent($payload);
|
||||
$this->dispatcher->dispatch($event, Events::JWT_DECODED);
|
||||
|
||||
if (!$event->isValid()) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'The token was marked as invalid by an event listener after successful decoding.');
|
||||
}
|
||||
|
||||
return $event->getPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user identity to payload, username by default.
|
||||
* Override this if you need to identify it by another property.
|
||||
*
|
||||
* @param array &$payload
|
||||
*/
|
||||
protected function addUserIdentityToPayload(UserInterface $user, array &$payload)
|
||||
{
|
||||
$accessor = PropertyAccess::createPropertyAccessor();
|
||||
$identityField = $this->userIdClaim ?: $this->userIdentityField;
|
||||
|
||||
if ($user instanceof InMemoryUser && 'username' === $identityField) {
|
||||
$payload[$identityField] = $accessor->getValue($user, 'userIdentifier');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$payload[$identityField] = $accessor->getValue($user, $accessor->isReadable($user, $identityField) ? $identityField : 'user_identifier');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserIdentityField(): string
|
||||
{
|
||||
if (0 === func_num_args() || func_get_arg(0)) {
|
||||
trigger_deprecation('lexik/jwt-authentication-bundle', '2.15', 'The "%s()" method is deprecated.', __METHOD__);
|
||||
}
|
||||
|
||||
return $this->userIdentityField;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUserIdentityField($field)
|
||||
{
|
||||
if (1 >= func_num_args() || func_get_arg(1)) {
|
||||
trigger_deprecation('lexik/jwt-authentication-bundle', '2.15', 'The "%s()" method is deprecated.', __METHOD__);
|
||||
}
|
||||
|
||||
$this->userIdentityField = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserIdClaim(): ?string
|
||||
{
|
||||
return $this->userIdClaim;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* JWTManagerInterface.
|
||||
*
|
||||
* @deprecated since 2.0, removed in 3.0. Use {@link JWTTokenManagerInterface} instead
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
interface JWTManagerInterface
|
||||
{
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
public function create(UserInterface $user);
|
||||
|
||||
/**
|
||||
* @return array|false The JWT token payload or false if an error occurs
|
||||
*/
|
||||
public function decode(TokenInterface $token);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* JWTTokenManagerInterface must be implemented by classes able to create/decode
|
||||
* JWT tokens.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
* @author Eric Lannez <eric.lannez@gmail.com>
|
||||
*
|
||||
* @method string createFromPayload(UserInterface $user, array $payload = []);
|
||||
* @method array parse(string $token) Parses a raw JWT token and returns its payload
|
||||
*/
|
||||
interface JWTTokenManagerInterface
|
||||
{
|
||||
/**
|
||||
* @return string The JWT token
|
||||
*/
|
||||
public function create(UserInterface $user);
|
||||
|
||||
/**
|
||||
* @return array|false The JWT token payload or false if an error occurs
|
||||
* @throws JWTDecodeFailureException
|
||||
*/
|
||||
public function decode(TokenInterface $token);
|
||||
|
||||
/**
|
||||
* Sets the field used as identifier to load an user from a JWT payload.
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @deprecated since 2.15, use {@see UserInterface::getUserIdentifier()} instead
|
||||
*/
|
||||
public function setUserIdentityField($field);
|
||||
|
||||
/**
|
||||
* Returns the field used as identifier to load an user from a JWT payload.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated since 2.15, use {@see UserInterface::getUserIdentifier()} instead
|
||||
*/
|
||||
public function getUserIdentityField();
|
||||
|
||||
/**
|
||||
* Returns the claim used as identifier to load an user from a JWT payload.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserIdClaim();
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
/**
|
||||
* Abstract class for key loaders.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractKeyLoader implements KeyLoaderInterface
|
||||
{
|
||||
private $signingKey;
|
||||
private $publicKey;
|
||||
private $passphrase;
|
||||
private $additionalPublicKeys;
|
||||
|
||||
public function __construct(?string $signingKey = null, ?string $publicKey = null, ?string $passphrase = null, array $additionalPublicKeys = [])
|
||||
{
|
||||
$this->signingKey = $signingKey;
|
||||
$this->publicKey = $publicKey;
|
||||
$this->passphrase = $passphrase;
|
||||
$this->additionalPublicKeys = $additionalPublicKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPassphrase()
|
||||
{
|
||||
return $this->passphrase;
|
||||
}
|
||||
|
||||
public function getSigningKey()
|
||||
{
|
||||
return $this->signingKey && is_file($this->signingKey) ? $this->readKey(self::TYPE_PRIVATE) : $this->signingKey;
|
||||
}
|
||||
|
||||
public function getPublicKey()
|
||||
{
|
||||
return $this->publicKey && is_file($this->publicKey) ? $this->readKey(self::TYPE_PUBLIC) : $this->publicKey;
|
||||
}
|
||||
|
||||
public function getAdditionalPublicKeys(): array
|
||||
{
|
||||
$contents = [];
|
||||
|
||||
foreach ($this->additionalPublicKeys as $key) {
|
||||
if (!$key || !is_file($key) || !is_readable($key)) {
|
||||
throw new \RuntimeException(sprintf('Additional public key "%s" does not exist or is not readable. Did you correctly set the "lexik_jwt_authentication.additional_public_keys" configuration key?', $key));
|
||||
}
|
||||
|
||||
$rawKey = $key;
|
||||
|
||||
if (is_file($key)) {
|
||||
$rawKey = @file_get_contents($key);
|
||||
|
||||
if (false === $rawKey) {
|
||||
// Try invalidating the realpath cache
|
||||
clearstatcache(true, $key);
|
||||
$rawKey = file_get_contents($key);
|
||||
}
|
||||
}
|
||||
$contents[] = $rawKey;
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type One of "public" or "private"
|
||||
*
|
||||
* @return string The path of the key, an empty string if not a valid path
|
||||
*
|
||||
* @throws \InvalidArgumentException If the given type is not valid
|
||||
* @throws \InvalidArgumentException If the given type is not valid
|
||||
*/
|
||||
protected function getKeyPath($type)
|
||||
{
|
||||
if (!in_array($type, [self::TYPE_PUBLIC, self::TYPE_PRIVATE])) {
|
||||
throw new \InvalidArgumentException(sprintf('The key type must be "public" or "private", "%s" given.', $type));
|
||||
}
|
||||
|
||||
$path = self::TYPE_PUBLIC === $type ? $this->publicKey : $this->signingKey;
|
||||
|
||||
if (!is_file($path) || !is_readable($path)) {
|
||||
throw new \RuntimeException(sprintf('%s key is not a file or is not readable.', ucfirst($type)));
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function readKey($type)
|
||||
{
|
||||
$isPublic = self::TYPE_PUBLIC === $type;
|
||||
$key = $isPublic ? $this->publicKey : $this->signingKey;
|
||||
|
||||
if (!$key || !is_file($key) || !is_readable($key)) {
|
||||
if ($isPublic) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('Signature key "%s" does not exist or is not readable. Did you correctly set the "lexik_jwt_authentication.signature_key" configuration key?', $key));
|
||||
}
|
||||
|
||||
$rawKey = @file_get_contents($key);
|
||||
|
||||
if (false === $rawKey) {
|
||||
// Try invalidating the realpath cache
|
||||
clearstatcache(true, $key);
|
||||
$rawKey = file_get_contents($key);
|
||||
}
|
||||
|
||||
return $rawKey;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
interface KeyDumperInterface
|
||||
{
|
||||
/**
|
||||
* Dumps a key to be shared between parties.
|
||||
*
|
||||
* @return resource|string
|
||||
*/
|
||||
public function dumpKey();
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
/**
|
||||
* Interface for classes that are able to load crypto keys.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @method string|null getPublicKey()
|
||||
* @method string|null getSigningKey()
|
||||
* @method array getAdditionalPublicKeys()
|
||||
*/
|
||||
interface KeyLoaderInterface
|
||||
{
|
||||
public const TYPE_PUBLIC = 'public';
|
||||
|
||||
public const TYPE_PRIVATE = 'private';
|
||||
|
||||
/**
|
||||
* Loads a key from a given type (public or private).
|
||||
*
|
||||
* @param resource|string|null $type
|
||||
*
|
||||
* @return resource|string|null
|
||||
*/
|
||||
public function loadKey($type);
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPassphrase();
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
@trigger_error(sprintf('The "%s\OpenSSLKeyLoader" class is deprecated since version 2.5 and will be removed in 3.0. Use "%s" instead.', __NAMESPACE__, RawKeyLoader::class), E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Load crypto keys for the OpenSSL crypto engine.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @deprecated since version 2.5, to be removed in 3.0. Use RawKeyLoader instead
|
||||
*/
|
||||
class OpenSSLKeyLoader extends AbstractKeyLoader implements KeyDumperInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \RuntimeException If the key cannot be read
|
||||
* @throws \RuntimeException Either the key or the passphrase is not valid
|
||||
*/
|
||||
public function loadKey($type)
|
||||
{
|
||||
if (!in_array($type, [self::TYPE_PUBLIC, self::TYPE_PRIVATE])) {
|
||||
throw new \InvalidArgumentException(sprintf('The key type must be "public" or "private", "%s" given.', $type));
|
||||
}
|
||||
|
||||
$keyPath = $this->getKeyPath($type);
|
||||
$rawKey = @file_get_contents($keyPath);
|
||||
|
||||
if (false === $rawKey) {
|
||||
// Try invalidating the realpath cache
|
||||
clearstatcache(true, $keyPath);
|
||||
$rawKey = file_get_contents($keyPath);
|
||||
}
|
||||
|
||||
$key = call_user_func_array("openssl_pkey_get_$type", self::TYPE_PRIVATE == $type ? [$rawKey, $this->getPassphrase()] : [$rawKey]);
|
||||
|
||||
if (!$key) {
|
||||
$sslError = '';
|
||||
while ($msg = trim(openssl_error_string(), " \n\r\t\0\x0B\"")) {
|
||||
if ('error:' === substr($msg, 0, 6)) {
|
||||
$msg = substr($msg, 6);
|
||||
}
|
||||
$sslError .= "\n $msg";
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('Failed to load %s key: %s', $type, $sslError));
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dumpKey()
|
||||
{
|
||||
$key = openssl_pkey_get_details($this->loadKey('public'));
|
||||
|
||||
if (!isset($key['key'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $key['key'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\KeyLoader;
|
||||
|
||||
/**
|
||||
* Reads crypto keys.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class RawKeyLoader extends AbstractKeyLoader implements KeyDumperInterface
|
||||
{
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \RuntimeException If the key cannot be read
|
||||
*/
|
||||
public function loadKey($type)
|
||||
{
|
||||
if (!in_array($type, [self::TYPE_PUBLIC, self::TYPE_PRIVATE])) {
|
||||
throw new \InvalidArgumentException(sprintf('The key type must be "public" or "private", "%s" given.', $type));
|
||||
}
|
||||
|
||||
if (self::TYPE_PUBLIC === $type) {
|
||||
return $this->dumpKey();
|
||||
}
|
||||
|
||||
return $this->getSigningKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dumpKey()
|
||||
{
|
||||
if ($publicKey = $this->getPublicKey()) {
|
||||
return $publicKey;
|
||||
}
|
||||
|
||||
$signingKey = $this->getSigningKey();
|
||||
|
||||
// no public key provided, compute it from signing key
|
||||
try {
|
||||
$publicKey = openssl_pkey_get_details(openssl_pkey_get_private($signingKey, $this->getPassphrase()))['key'];
|
||||
} catch (\Throwable $e) {
|
||||
throw new \RuntimeException('Secret key either does not exist, is not readable or is invalid. Did you correctly set the "lexik_jwt_authentication.secret_key" config option?');
|
||||
}
|
||||
|
||||
return $publicKey;
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken;
|
||||
|
||||
use Jose\Bundle\JoseFramework\Services\JWEBuilder;
|
||||
use Jose\Bundle\JoseFramework\Services\JWEBuilderFactory;
|
||||
use Jose\Bundle\JoseFramework\Services\JWSBuilder;
|
||||
use Jose\Bundle\JoseFramework\Services\JWSBuilderFactory;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Encryption\Serializer\CompactSerializer as JweCompactSerializer;
|
||||
use Jose\Component\Signature\Serializer\CompactSerializer as JwsCompactSerializer;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\BeforeJWEComputationEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
final class AccessTokenBuilder
|
||||
{
|
||||
/**
|
||||
* @var JWSBuilder
|
||||
*/
|
||||
private $jwsBuilder;
|
||||
|
||||
/**
|
||||
* @var null|JWEBuilder
|
||||
*/
|
||||
private $jweBuilder = null;
|
||||
|
||||
/**
|
||||
* @var JWK
|
||||
*/
|
||||
private $signatureKey;
|
||||
|
||||
/**
|
||||
* @var JWK|null
|
||||
*/
|
||||
private $encryptionKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $signatureAlgorithm;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $keyEncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $contentEncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
private $dispatcher;
|
||||
|
||||
public function __construct(
|
||||
EventDispatcherInterface $dispatcher,
|
||||
JWSBuilderFactory $jwsBuilderFactory,
|
||||
?JWEBuilderFactory $jweBuilderFactory,
|
||||
string $signatureAlgorithm,
|
||||
string $signatureKey,
|
||||
?string $keyEncryptionAlgorithm,
|
||||
?string $contentEncryptionAlgorithm,
|
||||
?string $encryptionKey
|
||||
) {
|
||||
$this->jwsBuilder = $jwsBuilderFactory->create([$signatureAlgorithm]);
|
||||
if ($jweBuilderFactory !== null && $keyEncryptionAlgorithm !== null && $contentEncryptionAlgorithm !== null) {
|
||||
$this->jweBuilder = $jweBuilderFactory->create([$keyEncryptionAlgorithm], [$contentEncryptionAlgorithm], []);
|
||||
}
|
||||
$this->signatureKey = JWK::createFromJson($signatureKey);
|
||||
$this->encryptionKey = $encryptionKey ? JWK::createFromJson($encryptionKey) : null;
|
||||
$this->signatureAlgorithm = $signatureAlgorithm;
|
||||
$this->keyEncryptionAlgorithm = $keyEncryptionAlgorithm;
|
||||
$this->contentEncryptionAlgorithm = $contentEncryptionAlgorithm;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
public function build(array $header, array $claims): string
|
||||
{
|
||||
$token = $this->buildJWS($header, $claims);
|
||||
|
||||
if ($this->jweBuilder !== null) {
|
||||
$token = $this->buildJWE($claims, $token);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $header
|
||||
* @param array<string, mixed> $claims
|
||||
*/
|
||||
private function buildJWS(array $header, array $claims): string
|
||||
{
|
||||
$header['alg'] = $this->signatureAlgorithm;
|
||||
if ($this->signatureKey->has('kid')) {
|
||||
$header['kid'] = $this->signatureKey->get('kid');
|
||||
}
|
||||
$jws = $this->jwsBuilder
|
||||
->create()
|
||||
->withPayload(json_encode($claims, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE))
|
||||
->addSignature($this->signatureKey, $header)
|
||||
->build()
|
||||
;
|
||||
$token = (new JwsCompactSerializer())->serialize($jws);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $header
|
||||
*/
|
||||
private function buildJWE(array $claims, string $payload): string
|
||||
{
|
||||
$header = [
|
||||
'alg' => $this->keyEncryptionAlgorithm,
|
||||
'enc' => $this->contentEncryptionAlgorithm,
|
||||
'cty' => 'JWT',
|
||||
'typ' => 'JWT',
|
||||
];
|
||||
if ($this->encryptionKey->has('kid')) {
|
||||
$header['kid'] = $this->encryptionKey->get('kid');
|
||||
}
|
||||
foreach (['exp', 'iat', 'nbf'] as $claim) {
|
||||
if (array_key_exists($claim, $claims)) {
|
||||
$header[$claim] = $claims[$claim];
|
||||
}
|
||||
}
|
||||
$event = $this->dispatcher->dispatch(new BeforeJWEComputationEvent($header), Events::BEFORE_JWE_COMPUTATION);
|
||||
$jwe = $this->jweBuilder
|
||||
->create()
|
||||
->withPayload($payload)
|
||||
->withSharedProtectedHeader($event->getHeader())
|
||||
->addRecipient($this->encryptionKey)
|
||||
->build()
|
||||
;
|
||||
return (new JweCompactSerializer())->serialize($jwe);
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken;
|
||||
|
||||
use Jose\Bundle\JoseFramework\Services\ClaimCheckerManager;
|
||||
use Jose\Bundle\JoseFramework\Services\ClaimCheckerManagerFactory;
|
||||
use Jose\Bundle\JoseFramework\Services\HeaderCheckerManager;
|
||||
use Jose\Bundle\JoseFramework\Services\JWELoader;
|
||||
use Jose\Bundle\JoseFramework\Services\JWELoaderFactory;
|
||||
use Jose\Bundle\JoseFramework\Services\JWSLoader;
|
||||
use Jose\Bundle\JoseFramework\Services\JWSLoaderFactory;
|
||||
use Jose\Component\Checker\InvalidClaimException;
|
||||
use Jose\Component\Checker\MissingMandatoryClaimException;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
|
||||
|
||||
final class AccessTokenLoader
|
||||
{
|
||||
private $jwsLoader;
|
||||
|
||||
private $jwsHeaderCheckerManager;
|
||||
|
||||
private $claimCheckerManager;
|
||||
|
||||
private $jweLoader;
|
||||
|
||||
private $signatureKeyset;
|
||||
|
||||
private $encryptionKeyset;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $mandatoryClaims;
|
||||
|
||||
private $continueOnDecryptionFailure;
|
||||
|
||||
public function __construct(
|
||||
JWSLoaderFactory $jwsLoaderFactory,
|
||||
?JWELoaderFactory $jweLoaderFactory,
|
||||
ClaimCheckerManagerFactory $claimCheckerManagerFactory,
|
||||
array $claimChecker,
|
||||
array $jwsHeaderChecker,
|
||||
array $mandatoryClaims,
|
||||
array $signatureAlgorithms,
|
||||
string $signatureKeyset,
|
||||
?bool $continueOnDecryptionFailure,
|
||||
?array $jweHeaderChecker,
|
||||
?array $keyEncryptionAlgorithms,
|
||||
?array $contentEncryptionAlgorithms,
|
||||
?string $encryptionKeyset
|
||||
) {
|
||||
$this->jwsLoader = $jwsLoaderFactory->create(['jws_compact'], $signatureAlgorithms, $jwsHeaderChecker);
|
||||
if ($jweLoaderFactory !== null && $keyEncryptionAlgorithms !== null && $contentEncryptionAlgorithms !== null && $jweHeaderChecker !== null) {
|
||||
$this->jweLoader = $jweLoaderFactory->create(['jwe_compact'], $keyEncryptionAlgorithms, $contentEncryptionAlgorithms, [], $jweHeaderChecker);
|
||||
$this->continueOnDecryptionFailure = $continueOnDecryptionFailure;
|
||||
}
|
||||
$this->signatureKeyset = JWKSet::createFromJson($signatureKeyset);
|
||||
$this->encryptionKeyset = $encryptionKeyset ? JWKSet::createFromJson($encryptionKeyset) : null;
|
||||
$this->claimCheckerManager = $claimCheckerManagerFactory->create($claimChecker);
|
||||
$this->mandatoryClaims = $mandatoryClaims;
|
||||
}
|
||||
|
||||
public function load(string $token): array
|
||||
{
|
||||
$token = $this->loadJWE($token);
|
||||
$data = $this->loadJWS($token);
|
||||
try {
|
||||
$this->claimCheckerManager->check($data, $this->mandatoryClaims);
|
||||
} catch (MissingMandatoryClaimException|InvalidClaimException $e) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, $e->getMessage(), $e, $data);
|
||||
} catch (\Throwable $e) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Unable to load the token', $e, $data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function loadJWS(string $token): array
|
||||
{
|
||||
$payload = null;
|
||||
$data = null;
|
||||
$signature = null;
|
||||
try {
|
||||
$jws = $this->jwsLoader->loadAndVerifyWithKeySet($token, $this->signatureKeyset, $signature);
|
||||
} catch (\Throwable $e) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be loaded or the signature cannot be verified.');
|
||||
}
|
||||
if ($signature !== 0) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token shall contain only one signature.');
|
||||
}
|
||||
|
||||
$payload = $jws->getPayload();
|
||||
if (!$payload) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.');
|
||||
}
|
||||
|
||||
$data = json_decode($payload, true);
|
||||
if (!is_array($data)) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function loadJWE(string $token): string
|
||||
{
|
||||
if (!$this->jweLoader) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$recipient = null;
|
||||
try {
|
||||
$jwe = $this->jweLoader->loadAndDecryptWithKeySet($token, $this->encryptionKeyset, $recipient);
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->continueOnDecryptionFailure === true) {
|
||||
return $token;
|
||||
}
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be decrypted.', $e);
|
||||
}
|
||||
$token = $jwe->getPayload();
|
||||
if (!$token || $recipient !== 0) {
|
||||
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token has no valid content.');
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Signature;
|
||||
|
||||
/**
|
||||
* Object representation of a newly created JSON Web Signature.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class CreatedJWS
|
||||
{
|
||||
/**
|
||||
* @deprecated since v2.11
|
||||
*/
|
||||
public const SIGNED = 'signed';
|
||||
|
||||
private $token;
|
||||
private $signed;
|
||||
|
||||
public function __construct(string $token, bool $isSigned)
|
||||
{
|
||||
$this->token = $token;
|
||||
$this->signed = $isSigned;
|
||||
}
|
||||
|
||||
public function isSigned(): bool
|
||||
{
|
||||
return $this->signed;
|
||||
}
|
||||
|
||||
public function getToken(): string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Signature;
|
||||
|
||||
/**
|
||||
* Object representation of a JSON Web Signature loaded from an
|
||||
* existing JSON Web Token.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class LoadedJWS
|
||||
{
|
||||
public const VERIFIED = 'verified';
|
||||
public const EXPIRED = 'expired';
|
||||
public const INVALID = 'invalid';
|
||||
|
||||
private $header;
|
||||
private $payload;
|
||||
private $state;
|
||||
private $clockSkew;
|
||||
private $shouldCheckExpiration;
|
||||
|
||||
public function __construct(array $payload, bool $isVerified, bool $shouldCheckExpiration = true, array $header = [], int $clockSkew = 0)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
$this->header = $header;
|
||||
$this->shouldCheckExpiration = $shouldCheckExpiration;
|
||||
$this->clockSkew = $clockSkew;
|
||||
|
||||
if (true === $isVerified) {
|
||||
$this->state = self::VERIFIED;
|
||||
}
|
||||
|
||||
$this->checkIssuedAt();
|
||||
$this->checkExpiration();
|
||||
}
|
||||
|
||||
public function getHeader(): array
|
||||
{
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
public function getPayload(): array
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
public function isVerified(): bool
|
||||
{
|
||||
return self::VERIFIED === $this->state;
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
$this->checkExpiration();
|
||||
|
||||
return self::EXPIRED === $this->state;
|
||||
}
|
||||
|
||||
public function isInvalid(): bool
|
||||
{
|
||||
return self::INVALID === $this->state;
|
||||
}
|
||||
|
||||
private function checkExpiration(): void
|
||||
{
|
||||
if (!$this->shouldCheckExpiration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($this->payload['exp']) || !is_numeric($this->payload['exp'])) {
|
||||
$this->state = self::INVALID;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->clockSkew <= time() - $this->payload['exp']) {
|
||||
$this->state = self::EXPIRED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the iat claim is not in the future.
|
||||
*/
|
||||
private function checkIssuedAt()
|
||||
{
|
||||
if (isset($this->payload['iat']) && (int) $this->payload['iat'] - $this->clockSkew > time()) {
|
||||
return $this->state = self::INVALID;
|
||||
}
|
||||
}
|
||||
}
|
||||
vendor/lexik/jwt-authentication-bundle/Subscriber/AdditionalAccessTokenClaimsAndHeaderSubscriber.php
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\Subscriber;
|
||||
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
final class AdditionalAccessTokenClaimsAndHeaderSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $ttl;
|
||||
|
||||
public function __construct(?int $ttl)
|
||||
{
|
||||
$this->ttl = $ttl;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
Events::JWT_CREATED => [
|
||||
['addClaims'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function addClaims(JWTCreatedEvent $event): void
|
||||
{
|
||||
$claims = [
|
||||
'jti' => uniqid('', true),
|
||||
'iat' => time(),
|
||||
'nbf' => time(),
|
||||
];
|
||||
$data = $event->getData();
|
||||
if (!array_key_exists('exp', $data) && $this->ttl > 0) {
|
||||
$claims['exp'] = time() + $this->ttl;
|
||||
}
|
||||
$event->setData(array_merge($claims, $data));
|
||||
}
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* AuthorizationHeaderTokenExtractor.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
class AuthorizationHeaderTokenExtractor implements TokenExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @param string|null $prefix
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($prefix, $name)
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extract(Request $request)
|
||||
{
|
||||
if (!$request->headers->has($this->name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$authorizationHeader = $request->headers->get($this->name);
|
||||
|
||||
if (empty($this->prefix)) {
|
||||
return $authorizationHeader;
|
||||
}
|
||||
|
||||
$headerParts = explode(' ', (string) $authorizationHeader);
|
||||
|
||||
if (!(2 === count($headerParts) && 0 === strcasecmp($headerParts[0], $this->prefix))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $headerParts[1];
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* ChainTokenExtractor is the class responsible of extracting a JWT token
|
||||
* from a {@link Request} object using all mapped token extractors.
|
||||
*
|
||||
* Note: The extractor map is reinitialized to the configured extractors for
|
||||
* each different instance.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class ChainTokenExtractor implements \IteratorAggregate, TokenExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $map;
|
||||
|
||||
public function __construct(array $map)
|
||||
{
|
||||
$this->map = $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new token extractor to the map.
|
||||
*/
|
||||
public function addExtractor(TokenExtractorInterface $extractor)
|
||||
{
|
||||
$this->map[] = $extractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a token extractor from the map.
|
||||
*
|
||||
* @param \Closure $filter A function taking an extractor as argument, used to find the extractor to remove.
|
||||
*
|
||||
* @return bool True in case of success, false otherwise
|
||||
*/
|
||||
public function removeExtractor(\Closure $filter)
|
||||
{
|
||||
$filtered = array_filter($this->map, $filter);
|
||||
|
||||
if (!$extractorToUnmap = current($filtered)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key = array_search($extractorToUnmap, $this->map);
|
||||
unset($this->map[$key]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the token extractor map.
|
||||
*/
|
||||
public function clearMap()
|
||||
{
|
||||
$this->map = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the token extractors map calling {@see extract()}
|
||||
* until a token is found.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extract(Request $request)
|
||||
{
|
||||
foreach ($this->getIterator() as $extractor) {
|
||||
if ($token = $extractor->extract($request)) {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the mapped token extractors while generating them.
|
||||
*
|
||||
* @return \Traversable|TokenExtractorInterface[]
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
foreach ($this->map as $extractor) {
|
||||
if ($extractor instanceof TokenExtractorInterface) {
|
||||
yield $extractor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* CookieTokenExtractor.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
class CookieTokenExtractor implements TokenExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extract(Request $request)
|
||||
{
|
||||
return $request->cookies->get($this->name, false);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* QueryParameterTokenExtractor.
|
||||
*
|
||||
* @author Nicolas Cabot <n.cabot@lexik.fr>
|
||||
*/
|
||||
class QueryParameterTokenExtractor implements TokenExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $parameterName;
|
||||
|
||||
/**
|
||||
* @param string $parameterName
|
||||
*/
|
||||
public function __construct($parameterName)
|
||||
{
|
||||
$this->parameterName = $parameterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extract(Request $request)
|
||||
{
|
||||
return $request->query->get($this->parameterName, false);
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* SplitCookieExtractor.
|
||||
*
|
||||
* @author Adam Lukacovic <adam@adamlukacovic.sk>
|
||||
*/
|
||||
class SplitCookieExtractor implements TokenExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cookies;
|
||||
|
||||
/**
|
||||
* @param array $cookies
|
||||
*/
|
||||
public function __construct($cookies)
|
||||
{
|
||||
$this->cookies = $cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function extract(Request $request)
|
||||
{
|
||||
$jwtCookies = [];
|
||||
|
||||
foreach ($this->cookies as $cookie) {
|
||||
$jwtCookies[] = $request->cookies->get($cookie, false);
|
||||
}
|
||||
|
||||
if (count($this->cookies) !== count(array_filter($jwtCookies))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return implode('.', $jwtCookies);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user