markitosgv / JWTRefreshTokenBundle

Implements a Refresh Token system over Json Web Tokens in Symfony
MIT License
663 stars 159 forks source link

refresh token with additional fields in payload #65

Closed nicraMarcin closed 5 years ago

nicraMarcin commented 7 years ago

Hello, I'm new in symfony and and try to set HWTRefreshTokenBundle with JTW Lexik. In jwt I have own Entity

namespace SharedBundle\Security;

final class User implements \Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserInterface
{
    private $username;
    private $roles;
    private $name;

    public function __construct($username, array $roles, $email, $name)
    {
        $this->username = $username;
        $this->roles = $roles;
        $this->email = $email;
        $this->name = $name;
    }

    public static function createFromPayload($username, array $payload)
    {
        return new self(
            $username,
            $payload['roles'], // Added by default
            $payload['email'],  // Custom
            $payload['name']
        );
    }

 /**
     * {@inheritdoc}
     */
    public function getUsername()
    {
        return $this->username;
    }
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return $this->name;
    }
    /**
     * {@inheritdoc}
     */
    public function getRoles()
    {
        return $this->roles;
    }
 ...

In listener I add custom data:

public function onJWTCreated(JWTCreatedEvent $event) {
    $request = $this->requestStack->getCurrentRequest();

    $payload = $event->getData();
    $user = $event->getUser();

    $payload['name'] = $user->getName();
    $payload['email'] = $user->getEmail();
    $payload['ip'] = $request->getClientIp();

    $event->setData($payload);
  }

But when I try to refresh token I get error:

[Mon May 29 22:07:36 2017] 127.0.0.1:56078 [200]: /api/token/refresh
[Mon May 29 22:13:52 2017] PHP Fatal error:  Call to undefined method Symfony\Component\Security\Core\User\User::getName() in .../application/be/src/SharedBundle/EventListener/JWTCreatedListener.php on line 33
Segmentation fault

When I throw email and name from payload token is created but without this data, and return only default role ROLE_USER without ROLE_ADMIN.

I'tried to use refresh_token_entity: SharedBundle\Security\User but without result :/ How can I implemet additional fields in payload?

thecassion commented 6 years ago

I have the same problem like you. How did you handle it @nicraMarcin ? This issue still exist. Did you use an other bundle or customize this one?

nicraMarcin commented 6 years ago

@thecassion I created my own custom refresh controller

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;

class TokenController extends Controller
{
    /**
     * @Rest\Get("/api/token/refresh")
     */
    public function indexAction()
    {
        $jwtManager = $this->container->get('lexik_jwt_authentication.jwt_manager');

        $token = $jwtManager->create($this->getUser());

        return [ 'token' => $token];
    }
}

now in frontend before token expires I make request to this route. to do is to check if user is still enabled.

thecassion commented 6 years ago

Ok . Thanks @nicraMarcin I understand . For me I don't use FOSRestBundle . I only use Api-platform . I will try to refresh token with my own logic like you do. Thanks again

remoteclient commented 6 years ago

I use this with Api Platform and it works. I use another Event I think:

use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use MWS\UserBundle\Model\UserInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class JWTAuthenticatedListener
{
    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @param TokenStorageInterface $tokenStorage
     */
    public function __construct(TokenStorageInterface $tokenStorage)
    {
        $this->tokenStorage = $tokenStorage;
    }

    /**
     *
     * @param AuthenticationSuccessEvent $event
     * @return void
     */
    public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $event)
    {
        $user = $event->getUser();

        if (!$user instanceof UserInterface) {
            return;
        }
        $data = $event->getData();
        $data['id'] = $user->getId();

        $event->setData($data);
    }
}

This does not encrypt the payload in the token but send the data with it. Encrypting additional data in the token wasn't possible.

Dont forget to give the listener a lower priority. The solution i found and helped me was this: https://github.com/gesdinet/JWTRefreshTokenBundle/issues/67

wittyweb commented 6 years ago

Using api-platform, I had the same problem and had to declare a custom user provider in the gesdinet_jwt_refresh_token.yaml configuration file (had to create this file in /config/packages) :

user_provider: security.user.provider.concrete.<your provider defined in security.yaml>

Then it used my User's entity in the onJWTCreated event and not the default one, and I had access to my custom method (like getId())

jspizziri commented 6 years ago

FWIW, I was also having an issue with the JWT generated after refresh. My issue was that on login, the JWT would contain the complete set of User roles, but on refresh, it wouldn't. The fix was simply to specify the user_provider in gesdinet config to be the same as the one used during initial authentication (in my case an fos_user.user_provider:

# gesdinet_jwt_refresh_token.yaml
gesdinet_jwt_refresh_token:
    user_provider: fos_user.user_provider.username_email
roelbeerens commented 5 years ago

@wittyweb You saved me there! Thanks!

peterforeman commented 5 years ago

@jspizziri This should be in the docs! Thanks!

steveKac01 commented 2 years ago

FWIW, I was also having an issue with the JWT generated after refresh. My issue was that on login, the JWT would contain the complete set of User roles, but on refresh, it wouldn't. The fix was simply to specify the user_provider in gesdinet config to be the same as the one used during initial authentication (in my case an fos_user.user_provider:

Hello, I have the same issue but i'm not using custom provider; can you help me please ? I'mmmm so stuck :')

jspizziri commented 2 years ago

@steveKac01 i haven't used symfony in several years. Good luck!

GregDevLab commented 2 years ago

@jspizziri @steveKac01 this is my first participation, I hope to help you 😉 JWTRefreshTokenBundle <== it works for me

 # config/packages/security.yaml
  app_user_provider: # the provider i use for refresh token
    entity:
      class: App\Entity\User
      property: username
  jwt:
    lexik_jwt:
      class: App\Security\UserAuthenticate
  firewalls:
    dev:
      pattern: ^/_(profiler|wdt)
      security: false
    login:
      # some config
    api:
      pattern: ^/api/
      stateless: true
      entry_point: jwt
      provider: jwt
      jwt: ~
      refresh_jwt:
        check_path: /api/token/refresh
        provider: app_user_provider # the user's provider must be declared here
# config/packages/gesdinet_jwt_refresh_token.yaml (automatically create from JWTRefreshTokenBundle recipes)
gesdinet_jwt_refresh_token:
  refresh_token_class:   @@App\Entity\RefreshToken