Symfony 6 and JWT Bundle: Refresh Token

*Cover image originally created by Geralt and edited with much appreciation.


summary

Are you interested in JSON Web Token (JWT) authentication and authorization in PHP or Symfony, one of its frameworks?

If so, this post may be helpful:

Well, the lifetime of each access token should be short within a practical period to reduce the risk on impersonation.
However, what should we do when the access token expires? Re-request authentication information from users? This must be inconvenient in many respects, right?

That’s where the refresh token steps in.
This post shows how to implement it in Symfony with JWTRefreshTokenBundle.
here is my.

environment


tutorial

Observation

Remember that you need to have LexikJWTAuthenticationBundle installed and your app pre-configured.

Here are the steps:

  1. bundle install
  2. PHP 8 specific operation (currently)
  3. update database
  4. configure
  5. test

1. bundle install

JWTRefreshTokenBundle is almost at your hand musician. run:

$ composer require gesdinet/jwt-refresh-token-bundle
enter fullscreen mode

exit fullscreen mode

Output started with:

Info from https://repo.packagist.org: #StandWithUkraine
Using version ^1.1 for gesdinet/jwt-refresh-token-bundle
./composer.json has been updated
Running composer update gesdinet/jwt-refresh-token-bundle
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking gesdinet/jwt-refresh-token-bundle (v1.1.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Downloading gesdinet/jwt-refresh-token-bundle (v1.1.1)
  - Installing gesdinet/jwt-refresh-token-bundle (v1.1.1): Extracting archive
Generating optimized autoload files
116 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
enter fullscreen mode

exit fullscreen mode

Then it followed:

Symfony operations: 1 recipe (44a1f19720c3d647b7a54653d52ca981)
  -  WARNING  gesdinet/jwt-refresh-token-bundle (>=1.0): From github.com/symfony/recipes-contrib:main
    The recipe for this package comes from the "contrib" repository, which is open to community contributions.
    Review the recipe at https://github.com/symfony/recipes-contrib/tree/main/gesdinet/jwt-refresh-token-bundle/1.0
enter fullscreen mode

exit fullscreen mode

Read the warning carefully and enter “y” to continue:

    Do you want to execute this recipe?
    [y] Yes
    [n] No
    [a] Yes for all packages, only for the current installation session
    [p] Yes permanently, never ask again for this project
    (defaults to n): y
enter fullscreen mode

exit fullscreen mode

The rest was:

  - Configuring gesdinet/jwt-refresh-token-bundle (>=1.0): From github.com/symfony/recipes-contrib:main
Executing script cache:clear [OK]
Executing script assets:install public [OK]

 What's next? 


Some files have been created and/or updated to configure your new packages.
Please review, edit and commit them: these files are yours.

No security vulnerability advisories found
enter fullscreen mode

exit fullscreen mode

2. Optimize it in PHP 8 (Currently)

Well, there was a problem. It was unfamiliar with PHP 8 and Symfony 6 by default, as it uses annotations. However, what was needed without Symfony Flex were features.
To fix this, edit src/Entity/RefreshToken.php,

- /**
-  * @ORM\Entity
-  * @ORM\Table("refresh_tokens")
-  */
+ #[ORM\Entity]
+ #[ORM\Table(name: 'refresh_token')]
enter fullscreen mode

exit fullscreen mode

then run:

$ composer install
enter fullscreen mode

exit fullscreen mode

3. Update Database

You are probably familiar with these command lines. run them:

$ php bin/console make:migration
$ php bin/console doctrine:migrations:migrate
enter fullscreen mode

exit fullscreen mode

4. Configure Routes and Firewalls to Refresh Tokens

edit config/routes.yaml

  # ...
  jwt_auth:
      path: /auth
+ jwt_refresh:
+     path: /auth/refresh
enter fullscreen mode

exit fullscreen mode

then edit config/packages/security.yaml

  security:
      # ...
      firewalls:
          # ...
          jwt_auth:
              pattern: ^/auth
              stateless: true
              json_login:
                  check_path: jwt_auth
                  success_handler: lexik_jwt_authentication.handler.authentication_success
                  failure_handler: lexik_jwt_authentication.handler.authentication_failure
          api:
              pattern: ^/api
              stateless: true
              jwt: ~
+             refresh_jwt:
+                 check_path: jwt_refresh
          # ...
     # Note: Only the *first* access control that matches will be used
     access_control:
         # ...
         - { path: ^/auth, roles: PUBLIC_ACCESS }
         - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
enter fullscreen mode

exit fullscreen mode

Also, optionally, you may want to integrate API routes of both authentication and access:

# config/routes.yaml
jwt_auth:
    path: /api/auth
jwt_refresh:
    path: /api/auth/refresh
enter fullscreen mode

exit fullscreen mode

,

# config/packages/security.yaml
security:
    # ...
    firewalls:
        # ...
        api:
            pattern: ^/api
            stateless: true
            json_login:
                check_path: jwt_auth
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
            jwt: ~
            entry_point: jwt
            refresh_jwt:
                check_path: jwt_refresh
    # ...
    access_control:
        - { path: ^/api/auth, roles: PUBLIC_ACCESS }
        - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
enter fullscreen mode

exit fullscreen mode

this much only!

5. Let’s Play: API Access with JWT

Like in my previous post, connect to /auth with curl to get the token:

$ curl -X POST \
      -H "Content-Type: application/json" \
      -d '{"username":"your-username","password":"your-password"}' \
      https://your-domain/auth
enter fullscreen mode

exit fullscreen mode

You will get a refresh token as well as an access token.

{"token":"xxx.xxx.xxx","refresh_token":"xxx"}
enter fullscreen mode

exit fullscreen mode

gotcha
Let’s try something with a refresh token!

missing case

$ curl -X POST \
      https://(your-domain)/auth/refresh
enter fullscreen mode

exit fullscreen mode

{"code":401,"message":"Missing JWT Refresh Token"}
enter fullscreen mode

exit fullscreen mode

Case of an invalid:

$ curl -X POST \
      -d refresh_token="wrong-value" \
      https://(your-domain)/auth/refresh
enter fullscreen mode

exit fullscreen mode

{"code":401,"message":"JWT Refresh Token Not Found"}
enter fullscreen mode

exit fullscreen mode

Valid case:

$ curl -X POST \
      -d refresh_token="xxx" \
      https://(your-domain)/auth/refresh
enter fullscreen mode

exit fullscreen mode

{"token":"xxx.xxx.xxx","refresh_token":"xxx"}
enter fullscreen mode

exit fullscreen mode

yay, here comes your new tokens

Leave a Comment