Can I use a multidimensional array as JWT Token payload?

39 views Asked by At

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?

2

There are 2 answers

0
Calamity Jane On BEST ANSWER

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);
        }
    }
}
1
Mohsin Khan On

Try this, To extract the uid property from the JSON string in PHP, you can decode the JSON string into an associative array using json_decode(), and then access the uid property as you would with any associative array. Here's how you can do it:

$jsonString = '{
    "application": {
        "appcode": "my_app"
    },
    "user": {
        "uid": "p320666",
        "firstname": "John",
        "mail": "John.Doe.com",
        "lastname": "Doe"
    },
    "grants": [
        "create"
    ],
    "resources": [
        {
            "code": "foo",
            "values": [
                "5"
            ]
        }
    ],
    "updateDate": {}
}';

// Decode the JSON string into an associative array
$data = json_decode($jsonString, true);

// Access the 'uid' property from the 'user' object
$uid = $data['user']['uid'];

echo $uid; // Output: p320666

In this code:

json_decode($jsonString, true) decodes the JSON string into an associative array. The second parameter true is used to specify that we want an associative array instead of an object. We then access the uid property using array notation ($data['user']['uid']). This will give you the value of the uid property from your JSON string.