welcome back to dyb-tech

This commit is contained in:
Daniel Guzman
2024-05-18 02:28:01 +02:00
parent 9513cdba09
commit 9f30bc98c7
6149 changed files with 668407 additions and 0 deletions
@@ -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;
}
}
@@ -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))
];
}
}
@@ -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');
}
}
}
@@ -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);
}
}
@@ -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))
;
}
}
@@ -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];
}
}
@@ -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'])
;
}
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
+93
View File
@@ -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';
}
}
@@ -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';
}
@@ -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
View File
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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()]);
}
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
@@ -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);
}
}
}
}
@@ -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;
}
}
@@ -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;
}
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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
);
}
}
@@ -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
}
}
@@ -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*/;
}
@@ -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);
}
}
@@ -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);
}
@@ -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();
}
@@ -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;
}
}
@@ -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();
}
@@ -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();
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
}
@@ -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));
}
}
@@ -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];
}
}
@@ -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;
}
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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