I am using JWT Token Authentication with lexik/jwt-authentication-bundle version 2.18.1. I have an API and my complete user information is stored in the JWT token I get from an external system. The information is an multidimensional array with the user identifier "uid" being stored in the user.
Example:
{
"application": {
"appcode": "my_app"
},
"user": {
"uid": "p320666",
"firstname": "John",
"mail": "John.Doe.com",
"lastname": "Doe"
},
"grants": [
"create"
],
"resources": [
{
"code": "foo",
"values": [
"5"
]
}
],
"updateDate": {}
}
My user identifier is uid and it is in the 2nd level of my information array.
If I set in my configuration user_id_claim: user
I get understandably the warning:
Array to string conversion
because in JWTAuthenticator.php
$passport = new SelfValidatingPassport(
new UserBadge(
(string)$payload[$idClaim],
function ($userIdentifier) use ($payload) {
return $this->loadUser($payload, $userIdentifier);
}
)
);
it tries to read my user array as a string.
If I set uid
in my configuration it isn't found. user.uid
also doesn't work.
Is there an option to access my uid
information without changing the structure of the payload?
I solved it now by writing a custom authenticator:
security.yaml
security:
enable_authenticator_manager: true
providers:
jwt:
lexik_jwt:
class: App\Security\User\User
[...]
firewalls: # selbstdefinierte Bereiche: dev oder doc können auch abc heißen
[...]
api:
pattern: ^/api
stateless: true
jwt:
authenticator: app.custom_authenticator
services.yaml
app.custom_authenticator:
class: App\Security\Authentication\JwtTokenAuthenticator
parent: lexik_jwt_authentication.security.jwt_authenticator
lexik_jwt_authentication.yaml
lexik_jwt_authentication:
[...]
pass_phrase: '%env(JWT_PASSPHRASE)%' # required for token creation
token_ttl: 3600 # token TTL in seconds, defaults to 1 hour
user_id_claim: user
and finally my custom authenticator where I changed the line (string) $payload[$idClaim]['uid'],
and also call an extra class TokenDataProcessor to extract my complete user info from the payload:
namespace App\Security\Authentication;
[...]
class JwtTokenAuthenticator extends JWTAuthenticator
{
private JWTTokenManagerInterface $jwtManager;
public function __construct(
JWTTokenManagerInterface $jwtManager,
EventDispatcherInterface $eventDispatcher,
TokenExtractorInterface $tokenExtractor,
UserProviderInterface $userProvider,
?TranslatorInterface $translator = null
) {
$this->jwtManager = $jwtManager;
parent::__construct($jwtManager, $eventDispatcher, $tokenExtractor, $userProvider, $translator);
}
[...]
public function doAuthenticate(Request $request): Passport
{
$token = $this->getTokenExtractor()->extract($request);
if (false === $token) {
$message = 'Unable to extract a JWT token from the request. Also, make sure to call `supports()`'
. ' before `authenticate()` to get a proper client error.';
throw new LogicException($message);
}
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]['uid'],
function ($userIdentifier) use ($payload) {
return $this->loadUser($payload, $userIdentifier);
}
)
);
$passport->setAttribute('payload', $payload);
$passport->setAttribute('token', $token);
return $passport;
}
public function loadUser(array $payload, string $identity): UserInterface
{
try {
$user = (new TokenDataProcessor())
->extract($payload);
return $user;
} catch (InvalidAuthDataException $e) {
$message = 'Authentication Failure with Credentials: ' . json_encode($payload)
. ' - ' . $e->getMessage();
$this->logInfo($message);
throw new ApiException(401, $message, $e, [], 7020);
}
}
}