WordPress REST API: JSON WEB Tokens (JWT) User Authentication

Let's code a plugin to authenticate user using JSON web tokens. For this we are going to use php jwt library from firebase and determine_current_user filter for setting up the user.

Create a plugin in WP plugin directory, you know the drill.

<?php  
/**
 * @wordpress-plugin
 * Plugin Name: JWT AUTH Tutorial
 */

// If this file is called directly, abort.
if (!defined('WPINC')) {  
    die;
}

After that, head on to https://github.com/firebase/php-jwt and install PHP library for JWT. So, let's add php-jwt to our plugin file.

require __DIR__ . '/vendor/autoload.php';  
use \Firebase\JWT\JWT;  

Now, let's create route for token generation.

Class JAT_REST_Auth {  
public     function __construct() {  
        $this->namespace = 'wp/v2';
        $this->rest_base = 'token';
    }
    public function register_routes() {
        register_rest_route($this->namespace, '/' . $this->rest_base, array(
            'methods' => 'POST',
            'callback' => array($this, 'generate_token'),
        )
        );
    }
}

$auth = new JAT_REST_Auth();
add_action('rest_api_init', array($auth, 'register_routes'));  

So we have registered a route wp/v2/token with the rest api, which will call a function generatetoken if we try access it via post method. Let's code the generatetoken function.

Class JAT_REST_Auth {  
    public function __construct() {
        $this->namespace = 'wp/v2';
        $this->rest_base = 'token';
    }
    public function register_routes() {
        register_rest_route($this->namespace, '/' . $this->rest_base, array(
            'methods' => 'POST',
            'callback' => array($this, 'generate_token'),
        )
        );
    }
    public function generate_token($request) {

        // Define a scret key in your config file
        $secret_key = defined('JAT_AUTH_SECRET_KEY') ? JAT_AUTH_SECRET_KEY : false;
        // Return error if secret key does not exist.
        if (!$secret_key) {
            return new WP_Error(
                'rest_auth_key',
                __('Secret key does not exist. How am i suppose to encrypt the data without a key?'),
                array('status' => 403)
            );
        }

        // Get username and password
        $username = $request->get_param('username');
        $password = $request->get_param('password');

        // Authenticate the user
        $user = wp_authenticate($username, $password);

        if (is_wp_error($user)) {
            return new WP_Error(
                'rest_auth_invalid_credentials',
                __('Username and password combination is not right buddy,'),
                array('status' => 403)
            );
        }

        // Credentials are valid
        // Generate a token
        $iat = time(); //Issued at
        $iss = get_bloginfo('url'); //issuer - server name.
        $exp = $iat + (DAY_IN_SECONDS * 30); //will expire after 30 days.

        $token_data = array(
            'iat' => $iat, //Issued at
            'iss' => $iss,
            'exp' => $exp,
            'data' => [
                'user' => [
                    'id' => $user->data->ID,
                ],
            ],
        );

        $token = JWT::encode($token_data, $secret_key);
        $data = array(
            'token' => $token,
            'user_email' => $user->data->user_email,
            'user_nicename' => $user->data->user_nicename,
            'user_display_name' => $user->data->display_name,
        );
        return $data;
    }
}

If we post our username and password on wp/v2/token it will respond with data containing a valid token, user email, nicename and display name.

So now we can generate token, let's code a function to validate the token.

Class JAT_REST_Auth {  
    public function __construct() {
        $this->namespace = 'wp/v2';
        $this->rest_base = 'token';
    }
    public function register_routes() {
        register_rest_route($this->namespace, '/' . $this->rest_base, array(
            'methods' => 'POST',
            'callback' => array($this, 'generate_token'),
        )
        );
    }
    public function generate_token($request) {

        // Define a scret key in your config file
        $secret_key = defined('JAT_AUTH_SECRET_KEY') ? JAT_AUTH_SECRET_KEY : false;
        // Return error if secret key does not exist.
        if (!$secret_key) {
            return new WP_Error(
                'rest_auth_key',
                __('Secret key does not exist. How am i suppose to encrypt the data without a key?'),
                array('status' => 403)
            );
        }

        // Get username and password
        $username = $request->get_param('username');
        $password = $request->get_param('password');

        // Authenticate the user
        $user = wp_authenticate($username, $password);

        if (is_wp_error($user)) {
            return new WP_Error(
                'rest_auth_invalid_credentials',
                __('Username and password combination is not right buddy,'),
                array('status' => 403)
            );
        }

        // Credentials are valid
        // Generate a token
        $iat = time(); //Issued at
        $iss = get_bloginfo('url'); //issuer - server name.
        $exp = $iat + (DAY_IN_SECONDS * 30); //will expire after 30 days.

        $token_data = array(
            'iat' => $iat, //Issued at
            'iss' => $iss,
            'exp' => $exp,
            'data' => [
                'user' => [
                    'id' => $user->data->ID,
                ],
            ],
        );

        $token = JWT::encode($token_data, $secret_key);
        $data = array(
            'token' => $token,
            'user_email' => $user->data->user_email,
            'user_nicename' => $user->data->user_nicename,
            'user_display_name' => $user->data->display_name,
        );
        return $data;
    }

    public function validate_token() {
        // Define a scret key in your config file
        $secret_key = defined('GRUPPO_AUTH_SECRET_KEY') ? GRUPPO_AUTH_SECRET_KEY : false;
        // Return error if secret key does not exist.
        if (!$secret_key) {
            return new WP_Error(
                'rest_auth_key',
                __('Secret key does not exist. How am i suppose to encrypt the data without a key?'),
                array('status' => 403)
            );
        }
// Get the authorization header
        $auth = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : false;

// Check if it exists or not
        if (!$auth) {
            return new WP_Error(
                'rest_auth_no_header',
                __('Authorization header not found'),
                array('status' => 403)
            );
        }

        list($jwt) = sscanf($auth, 'Bearer %s');

        if (!$jwt) {
            return new WP_Error(
                'rest_auth_no_token',
                __('No token found in authorization header?'),
                array('status' => 403)
            );
        }

        //Let's decode the token
        try {
            $token = JWT::decode($jwt, $secret_key, array('HS256'));

            // check for issuer
            if ($token->iss != get_bloginfo('url')) {
                return new WP_Error(
                    'rest_auth_wrong_issuer',
                    __('Wrong server brother.'),
                    array('status' => 403)
                );
            }

            // Check for userid
            if (!isset($token->data->user->id)) {
                return new WP_Error(
                    'rest_auth_invalid',
                    __('Invalid token.'),
                    array('status' => 403)
                );
            }

            return $token;

        } catch (Exception $e) {
            return new WP_Error(
                'rest_auth_invalid_token',
                $e->getMessage(),
                array('status' => 403)
            );
        }

    }

}

With every request you have to pass the token which we generated in previous step. Header key should be Authorization and value should be Bearer TOKEN

Now let's add a filter to determine_current_user to check for token and for setting up user.

Class JAT_REST_Auth {  
    public function __construct() {
        $this->namespace = 'wp/v2';
        $this->rest_base = 'token';
    }
    public function register_routes() {
        register_rest_route($this->namespace, '/' . $this->rest_base, array(
            'methods' => 'POST',
            'callback' => array($this, 'generate_token'),
        )
        );
    }
    public function generate_token($request) {

        // Define a scret key in your config file
        $secret_key = defined('JAT_AUTH_SECRET_KEY') ? JAT_AUTH_SECRET_KEY : false;
        // Return error if secret key does not exist.
        if (!$secret_key) {
            return new WP_Error(
                'rest_auth_key',
                __('Secret key does not exist. How am i suppose to encrypt the data without a key?'),
                array('status' => 403)
            );
        }

        // Get username and password
        $username = $request->get_param('username');
        $password = $request->get_param('password');

        // Authenticate the user
        $user = wp_authenticate($username, $password);

        if (is_wp_error($user)) {
            return new WP_Error(
                'rest_auth_invalid_credentials',
                __('Username and password combination is not right buddy,'),
                array('status' => 403)
            );
        }

        // Credentials are valid
        // Generate a token
        $iat = time(); //Issued at
        $iss = get_bloginfo('url'); //issuer - server name.
        $exp = $iat + (DAY_IN_SECONDS * 30); //will expire after 30 days.

        $token_data = array(
            'iat' => $iat, //Issued at
            'iss' => $iss,
            'exp' => $exp,
            'data' => [
                'user' => [
                    'id' => $user->data->ID,
                ],
            ],
        );

        $token = JWT::encode($token_data, $secret_key);
        $data = array(
            'token' => $token,
            'user_email' => $user->data->user_email,
            'user_nicename' => $user->data->user_nicename,
            'user_display_name' => $user->data->display_name,
        );
        return $data;
    }
    public function validate_token() {
        // Define a secret key in your config file
        $secret_key = defined('GRUPPO_AUTH_SECRET_KEY') ? GRUPPO_AUTH_SECRET_KEY : false;
        // Return error if secret key does not exist.
        if (!$secret_key) {
            return new WP_Error(
                'rest_auth_key',
                __('Secret key does not exist. How am i suppose to encrypt the data without a key?'),
                array('status' => 403)
            );
        }
        // Get the authorization header
        $auth = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : false;

        // Check if it exists or not
        if (!$auth) {
            return new WP_Error(
                'rest_auth_no_header',
                __('Authorization header not found'),
                array('status' => 403)
            );
        }

        list($jwt) = sscanf($auth, 'Bearer %s');

        if (!$jwt) {
            return new WP_Error(
                'rest_auth_no_token',
                __('No token found in authorization header?'),
                array('status' => 403)
            );
        }

        //Let's decode the token
        try {
            $token = JWT::decode($jwt, $secret_key, array('HS256'));

            // check for issuer
            if ($token->iss != get_bloginfo('url')) {
                return new WP_Error(
                    'rest_auth_wrong_issuer',
                    __('Wrong server brother.'),
                    array('status' => 403)
                );
            }

            // Check for userid
            if (!isset($token->data->user->id)) {
                return new WP_Error(
                    'rest_auth_invalid',
                    __('Invalid token.'),
                    array('status' => 403)
                );
            }

            return $token;

        } catch (Exception $e) {
            return new WP_Error(
                'rest_auth_invalid_token',
                $e->getMessage(),
                array('status' => 403)
            );
        }

    }

    public function determine_current_user($user) {
        $token = $this->validate_token();

        if (is_wp_error($token)) {
            return $user;
        }
        return $token->data->user->id;
    }
}

$auth = new JAT_REST_Auth();
add_action('rest_api_init', array($auth, 'register_routes'));  
add_filter('determine_current_user', array($auth, 'determine_current_user'), 20);  

So determinecurrentuser function of class JATRESTAuth will hook into the determinecurrentuser filter and check for a valid token and return the user id accordingly.

Here is the complete code.

<?php  
require __DIR__ . '/vendor/autoload.php';  
require_once plugin_dir_path(__FILE__) . 'libs/cpt.php';  
use \Firebase\JWT\JWT;

Class JAT_REST_Auth {  
    public function __construct() {
        $this->namespace = 'wp/v2';
        $this->rest_base = 'token';
    }
    public function register_routes() {
        register_rest_route($this->namespace, '/' . $this->rest_base, array(
            'methods' => 'POST',
            'callback' => array($this, 'generate_token'),
        )
        );
    }
    public function generate_token($request) {

        // Define a scret key in your config file
        $secret_key = defined('JAT_AUTH_SECRET_KEY') ? JAT_AUTH_SECRET_KEY : false;
        // Return error if secret key does not exist.
        if (!$secret_key) {
            return new WP_Error(
                'rest_auth_key',
                __('Secret key does not exist. How am i suppose to encrypt the data without a key?'),
                array('status' => 403)
            );
        }

        // Get username and password
        $username = $request->get_param('username');
        $password = $request->get_param('password');

        // Authenticate the user
        $user = wp_authenticate($username, $password);

        if (is_wp_error($user)) {
            return new WP_Error(
                'rest_auth_invalid_credentials',
                __('Username and password combination is not right buddy,'),
                array('status' => 403)
            );
        }

        // Credentials are valid
        // Generate a token
        $iat = time(); //Issued at
        $iss = get_bloginfo('url'); //issuer - server name.
        $exp = $iat + (DAY_IN_SECONDS * 30); //will expire after 30 days.

        $token_data = array(
            'iat' => $iat, //Issued at
            'iss' => $iss,
            'exp' => $exp,
            'data' => [
                'user' => [
                    'id' => $user->data->ID,
                ],
            ],
        );

        $token = JWT::encode($token_data, $secret_key);
        $data = array(
            'token' => $token,
            'user_email' => $user->data->user_email,
            'user_nicename' => $user->data->user_nicename,
            'user_display_name' => $user->data->display_name,
        );
        return $data;
    }
    public function validate_token() {
        // Define a secret key in your config file
        $secret_key = defined('GRUPPO_AUTH_SECRET_KEY') ? GRUPPO_AUTH_SECRET_KEY : false;
        // Return error if secret key does not exist.
        if (!$secret_key) {
            return new WP_Error(
                'rest_auth_key',
                __('Secret key does not exist. How am i suppose to encrypt the data without a key?'),
                array('status' => 403)
            );
        }
        // Get the authorization header
        $auth = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : false;

        // Check if it exists or not
        if (!$auth) {
            return new WP_Error(
                'rest_auth_no_header',
                __('Authorization header not found'),
                array('status' => 403)
            );
        }

        list($jwt) = sscanf($auth, 'Bearer %s');

        if (!$jwt) {
            return new WP_Error(
                'rest_auth_no_token',
                __('No token found in authorization header?'),
                array('status' => 403)
            );
        }

        //Let's decode the token
        try {
            $token = JWT::decode($jwt, $secret_key, array('HS256'));

            // check for issuer
            if ($token->iss != get_bloginfo('url')) {
                return new WP_Error(
                    'rest_auth_wrong_issuer',
                    __('Wrong server brother.'),
                    array('status' => 403)
                );
            }

            // Check for userid
            if (!isset($token->data->user->id)) {
                return new WP_Error(
                    'rest_auth_invalid',
                    __('Invalid token.'),
                    array('status' => 403)
                );
            }

            return $token;

        } catch (Exception $e) {
            return new WP_Error(
                'rest_auth_invalid_token',
                $e->getMessage(),
                array('status' => 403)
            );
        }

    }

    public function determine_current_user($user) {
        $token = $this->validate_token();

        if (is_wp_error($token)) {
            return $user;
        }
        return $token->data->user->id;
    }
}

$auth = new JAT_REST_Auth();
add_action('rest_api_init', array($auth, 'register_routes'));  
add_filter('determine_current_user', array($auth, 'determine_current_user'), 20);  
comments powered by Disqus