DEV Community

Cover image for Symfony 6 and JWT bundles: Refresh token
nabbisen
nabbisen

Posted on • Edited on • Originally published at scqr.net

Symfony 6 and JWT bundles: Refresh token

* The cover image is originally by geralt and edited with great 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 might be helpful:

Well, lifetime of each access token should be short within practical term in order to mitigate risk on impersonation.
However, when access token is expired, what should we do? Request authentication information to users, again? It must be inconvenient in many cases, mustn't it πŸ˜…

That's where refresh token steps in.
This post shows how to implement it in Symfony with JWTRefreshTokenBundle.
Here we go.

Environment


Tutorial

Overview

Remember you need install LexikJWTAuthenticationBundle and configure your app beforehand.

The steps here are as follows:

  1. Install the bundle
  2. PHP 8 specific operation (currently)
  3. Update database
  4. Configure
  5. Testing

1. Install the bundle

JWTRefreshTokenBundle is almost in your hand with composer. Run:

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

The 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 was followed by:

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. Adapt it to PHP 8 (currently)

Well, there was a problem. It was unfamiliar with PHP 8 and Symfony 6 by default, because it uses annotations. However, what was required without Symfony flex was attributes.
To fix it, 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 perhaps 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 for 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

In addition, alternatively, you may want to integrate API routes of both auth and use:

# 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

That's it!

5. Let's play: API access with JWT

Just as my previous post, connect to /auth with curl to get 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 refresh token as well as access token.

{"token":"xxx.xxx.xxx","refresh_token":"xxx"} 
Enter fullscreen mode Exit fullscreen mode

Gotcha 😎
Let's try a few with the refresh token!

(1) Case of missing 😩

$ 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

(2) Case of an invalid one 😩

$ 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

(3) Case of the valid one πŸ˜ƒ

$ 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 come new tokens of yours πŸš€ 🎫🎫

Top comments (0)