I've been attempting to configure LexikJWTAuthenticationBundle for use in a project based on API Platform.
This API is being added to an existing project that uses Sulu CMS (2.6.2) and Symfony (6.4.7). I'm using PHP 8.3 and have OpenSSL installed and enabled.
I'd like to be able to use the LexikJWTAuthenticationBundle's web-token feature. I've been able to get the following configuration, which is based on the bundle's defaults, to work:
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
api_platform:
enabled: true
check_path: /api/login_check
username_path: username
password_path: password
encoder:
service: lexik_jwt_authentication.encoder.lcobucci
signature_algorithm: ES512
token_ttl: 3600
allow_no_expiration: false
clock_skew: 0
user_id_claim: username
token_extractors:
authorization_header:
enabled: true
prefix: Bearer
name: Authorization
cookie:
enabled: false
name: BEARER
query_parameter:
enabled: false
name: bearer
split_cookie:
enabled: false
cookies: { }
remove_token_from_body_when_cookies_used: true
set_cookies: { }
I've followed the documentation for configuring the web-token feature. Unfortunately, everything blows up once I switch over to using lexik_jwt_authentication.encoder.web_token
. JWT tokens are always invalid.
I've written my own user provider service to avoid conflicts with Sulu and have verified that it works with the bundle's default encoder.
My bundle configuration after switching to the web-token encoder:
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
api_platform:
enabled: true
check_path: /api/login_check
username_path: username
password_path: password
encoder:
service: lexik_jwt_authentication.encoder.web_token
signature_algorithm: ES512
token_ttl: 3600
allow_no_expiration: false
clock_skew: 0
user_id_claim: username
token_extractors:
authorization_header:
enabled: true
prefix: Bearer
name: Authorization
cookie:
enabled: false
name: BEARER
query_parameter:
enabled: false
name: bearer
split_cookie:
enabled: false
cookies: { }
remove_token_from_body_when_cookies_used: true
set_cookies: { }
access_token_issuance:
enabled: true
signature:
algorithm: ES512
key: # key
access_token_verification:
enabled: true
signature:
allowed_algorithms:
- ES512
keyset: # keyset
header_checkers: { }
claim_checkers:
- exp_with_clock_skew
- iat_with_clock_skew
- nbf_with_clock_skew
mandatory_claims: { }
blocklist_token:
enabled: false
cache: cache.app
How I'm requesting a token:
curl -X POST http://localhost/api/login_check \
-H 'Content-Type: application/ld+json' \
-d '{"username":"someusername","password":"somepassword"}'
The request I'm making after receiving a token:
curl -H 'Accept: application/ld+json' \
-H 'Authorization: Bearer <token>' http://localhost/api/some-endpoint/4
I've been able to track the issue down to the stage where the token is loaded and decoded. It's correctly unserialized and broken down into an array. It fails after that point. This makes me suspect there's a problem with the encoding for my keys, but I'm not sure what I'm doing wrong.
I've used the web-token/jwt-bundle
's commands to generate keys and I've tried using the standalone JWT app. I've analyzed the results with the bundle's key:analyze
and keyset:analyze
commands. They appear to be correct. And I've looked at related questions on here to see if someone else has had a similar problem. Unfortunately, that hasn't helped.
I'd greatly appreciate any insight anyone might have on this. I've definitely hit a wall. Thanks in advance!
EDIT: After some more debugging, it looks like encryption is mandatory for this bundle if using the web-token feature even though the documentation says otherwise. Since I didn't have encryption configured, all of my requests using the token were being rejected due to an undefined algorithm.
Encryption is working but decryption is not. It gets to the decryption part of the process and then fails. So, I'm still getting an Invalid JWT Token
response.
My full configuration with encryption enabled:
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_PRIVATE_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
api_platform:
enabled: true
check_path: /api/login_check
username_path: username
password_path: password
encoder:
service: lexik_jwt_authentication.encoder.web_token
signature_algorithm: ES512
token_ttl: 3600
allow_no_expiration: false
clock_skew: 0
user_id_claim: username
token_extractors:
authorization_header:
enabled: true
prefix: Bearer
name: Authorization
cookie:
enabled: false
name: BEARER
query_parameter:
enabled: false
name: bearer
split_cookie:
enabled: false
cookies: { }
remove_token_from_body_when_cookies_used: true
set_cookies: { }
access_token_issuance:
enabled: true
signature:
algorithm: ES512
key: # key
encryption:
enabled: true
key_encryption_algorithm: RSA-OAEP-256
content_encryption_algorithm: A256CBC-HS512
key: # key
access_token_verification:
enabled: true
signature:
allowed_algorithms:
- ES512
keyset: # keyset
header_checkers: { }
claim_checkers:
- exp_with_clock_skew
- iat_with_clock_skew
- nbf_with_clock_skew
mandatory_claims: { }
encryption:
enabled: true
continue_on_decryption_failure: false
header_checkers:
- iat_with_clock_skew
- nbf_with_clock_skew
- exp_with_clock_skew
allowed_key_encryption_algorithms:
- RSA-OAEP-256
allowed_content_encryption_algorithms:
- A256CBC-HS512
keyset: # keyset
blocklist_token:
enabled: false
cache: cache.app
When I initially posted this question, I was unsure if the issue I was having was a bug or a configuration problem on my end. I had opened an issue in the official repository as well.
Today I was able to dig into this some more and it does indeed seem to be a bug. I posted a workaround in the GitHub issue along with additional information. But I'll post it here as well for the sake of completeness for anyone who just wants a working solution without needing additional details.
As a workaround until the bug is fixed, I extended my Kernel
class by implementing CompilerPassInterface
as explained in the Symfony documentation How to Work with Compiler Passes. Overriding the default encryption configuration values with null
prevents exceptions from being thrown and allows both access token issuance and verification to work correctly.
Note that this workaround only works if you aren't using encryption. I still haven't been able to get decryption working. But that's a separate issue.
// src/Kernel.php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
// ...
public function process(ContainerBuilder $container): void
{
$accessTokenBuilderService = 'lexik_jwt_authentication.access_token_builder';
$accessTokenLoaderService = 'lexik_jwt_authentication.access_token_loader';
if ($container->hasDefinition($accessTokenBuilderService)) {
$container->getDefinition($accessTokenBuilderService)
->replaceArgument(5, null)
->replaceArgument(6, null)
->replaceArgument(7, null);
}
if ($container->hasDefinition($accessTokenLoaderService)) {
$container->getDefinition($accessTokenLoaderService)
->replaceArgument(9, null)
->replaceArgument(10, null)
->replaceArgument(11, null)
->replaceArgument(12, null);
}
}
// ...
}
The following example configuration works with the above Kernel
implementation. Change as needed.
# config/services.yaml
parameters:
# ...
env(SIGNATURE_KEY): '%kernel.project_dir%/config/jwt/signature.jwk'
env(SIGNATURE_KEYSET): '%kernel.project_dir%/config/jwt/signature.jwkset'
# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_PRIVATE_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
api_platform:
enabled: true
check_path: /api/login_check
username_path: username
password_path: password
encoder:
service: lexik_jwt_authentication.encoder.web_token
token_ttl: 3600
allow_no_expiration: false
clock_skew: 0
user_id_claim: username
token_extractors:
authorization_header:
enabled: true
prefix: Bearer
name: Authorization
access_token_issuance:
enabled: true
signature:
algorithm: RS256
key: '%env(file:SIGNATURE_KEY)%'
access_token_verification:
enabled: true
signature:
allowed_algorithms:
- RS256
keyset: '%env(file:SIGNATURE_KEYSET)%'
header_checkers: { }
claim_checkers:
- exp_with_clock_skew
- iat_with_clock_skew
- nbf_with_clock_skew
mandatory_claims: { }
blocklist_token:
enabled: false
cache: cache.app