phpjwtlexikjwtauthbundle

Can I use a multidimensional array as JWT Token payload?


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?


Solution

  • 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);
            }
        }
    }