April 30, 2020

Using PHP-JWT to Implement Token and Token Refreshing

The php-jwt library is a very useful mechanism for token-based authentication. It works especially well for securing user authentication in apps. However, all tokens come with an expiration time. So what happens when a token expires? How can we refresh it seamlessly, without the user noticing?

The key is for the frontend to handle token expiration. When the token is expired, the backend will respond with a message indicating expiration. We can define a specific error code (e.g., 1002) for this scenario. Upon receiving this code, the frontend can automatically call the refresh token API and continue with the original request. Let’s walk through the implementation using the ThinkPHP framework and JWT.


Step 1: Install the JWT Package

Use Composer to install the JWT extension for ThinkPHP:

composer require thans/tp-jwt-auth

Then generate the JWT configuration file:

php think jwt:create

Step 2: First-Time Login to Get a Token

Example frontend code to fetch the token:

function get_token() {
    $.ajax({
        type: "GET",
        url: "{:url('index/index/token')}",
        dataType: "json",    
        success: (res) => {
            localStorage.setItem('token', res.token)
        }
    });
}

Controller code to generate the token:

use thans\jwt\facade\JWTAuth;

public function token()
{
    $token = JWTAuth::builder([
        'userInfo' => [
            'id' => 1,
            'username' => 'jeevin'
        ]
    ]);
    return json(['code' => 200, 'msg' => 'success', 'token' => $token]);
}

Step 3: Access Protected Resources Using the Token

Example frontend code to access user data:

function get_user() {
    let isRefreshing = true; // Prevent multiple refresh requests
    $.ajax({
        type: "GET",
        url: "{:url('index/index/user')}",
        data: { token: localStorage.getItem('token') },
        dataType: "json",
        success: (res) => {
            if (res.code === 1002) {
                if (isRefreshing) {
                    refreshTokenRequest(); // Refresh the token if expired
                }
            }
            isRefreshing = false;
        }
    });
}

Function to refresh the token:

function refreshTokenRequest() {
    $.ajax({
        type: "GET",
        url: "{:url('index/index/refesh_token')}",
        data: { token: localStorage.getItem('token') },
        dataType: "json",
        success: (res) => {
            if (res.code === 200) {
                localStorage.setItem('token', res.token);
            }
        }
    });
}

Step 4: Backend Logic for Protected Resources and Token Refresh

Get user info:

public function user()
{
    try {
        $token = JWTAuth::auth();
        return json(['code' => 200, 'msg' => 'Success', 'token' => $token]);
    } catch (\thans\jwt\exception\TokenExpiredException $e) {
        return json(['code' => 1002, 'msg' => 'Token expired']);
    } catch (\Exception $e) {
        return json(['code' => 400, 'msg' => $e->getMessage()]);
    }
}

Refresh token API:

public function RefeshToken()
{
    $token = JWTAuth::refresh();
    return json(['code' => 200, 'msg' => 'Success', 'token' => $token]);
}

Conclusion

With php-jwt and a little help from the frontend, you can easily implement a secure and user-friendly token-based authentication system in ThinkPHP. By handling token expiration on the client side and using a refresh token endpoint, users can stay authenticated without any interruptions.