Skip to content

Instantly share code, notes, and snippets.

@jparciga
Created May 17, 2022 15:38
Show Gist options
  • Save jparciga/144dd3e2b2140e2114fec1012048d13f to your computer and use it in GitHub Desktop.
Save jparciga/144dd3e2b2140e2114fec1012048d13f to your computer and use it in GitHub Desktop.
JWT Auth Plugin changes
<?php
/** Requiere the JWT library. */
use \Firebase\JWT\JWT;
/**
* The public-facing functionality of the plugin.
*
* @link https://enriquechavez.co
* @since 1.0.0
*/
/**
* The public-facing functionality of the plugin.
*
* Defines the plugin name, version, and two examples hooks for how to
* enqueue the admin-specific stylesheet and JavaScript.
*
* @author Enrique Chavez <noone@tmeister.net>
*/
class Jwt_Auth_Public
{
/**
* The ID of this plugin.
*
* @since 1.0.0
*
* @var string The ID of this plugin.
*/
private $plugin_name;
/**
* The version of this plugin.
*
* @since 1.0.0
*
* @var string The current version of this plugin.
*/
private $version;
/**
* The namespace to add to the api calls.
*
* @var string The namespace to add to the api call
*/
private $namespace;
/**
* Store errors to display if the JWT is wrong
*
* @var WP_Error
*/
private $jwt_error = null;
/**
* Initialize the class and set its properties.
*
* @since 1.0.0
*
* @param string $plugin_name The name of the plugin.
* @param string $version The version of this plugin.
*/
public function __construct($plugin_name, $version)
{
$this->plugin_name = $plugin_name;
$this->version = $version;
$this->namespace = $this->plugin_name . '/v' . intval($this->version);
}
/**
* Add the endpoints to the API
*/
public function add_api_routes()
{
register_rest_route($this->namespace, 'token', array(
'methods' => 'POST',
'callback' => array($this, 'generate_token'),
));
register_rest_route($this->namespace, 'token/validate', array(
'methods' => 'POST',
'callback' => array($this, 'validate_token'),
));
}
/**
* Add CORs suppot to the request.
*/
public function add_cors_support()
{
$enable_cors = defined('JWT_AUTH_CORS_ENABLE') ? JWT_AUTH_CORS_ENABLE : false;
if ($enable_cors) {
$headers = apply_filters('jwt_auth_cors_allow_headers', 'Access-Control-Allow-Headers, Content-Type, Authorization');
header(sprintf('Access-Control-Allow-Headers: %s', $headers));
}
}
/**
* Get the user and password in the request body and generate a JWT
*
* @param [type] $request [description]
*
* @return [type] [description]
*/
public function generate_token($request)
{
$secret_key = defined('JWT_AUTH_SECRET_KEY') ? JWT_AUTH_SECRET_KEY : false;
$username = $request->get_param('username');
$password = $request->get_param('password');
/** First thing, check the secret key if not exist return a error*/
if (!$secret_key) {
return new WP_Error(
'jwt_auth_bad_config',
__('JWT is not configurated properly, please contact the admin', 'wp-api-jwt-auth'),
array(
'status' => 403,
)
);
}
/** Try to authenticate the user with the passed credentials*/
$user = wp_authenticate($username, $password);
/** If the authentication fails return a error*/
if (is_wp_error($user)) {
$error_code = $user->get_error_code();
return new WP_Error(
'[jwt_auth] ' . $error_code,
$user->get_error_message($error_code),
array(
'status' => 403,
)
);
}
/** Valid credentials, the user exists create the according Token */
$issuedAt = time();
$notBefore = apply_filters('jwt_auth_not_before', $issuedAt, $issuedAt);
$expire = apply_filters('jwt_auth_expire', $issuedAt + (DAY_IN_SECONDS * 7), $issuedAt);
$token = array(
'iss' => get_bloginfo('url'),
'iat' => $issuedAt,
'nbf' => $notBefore,
'exp' => $expire,
'data' => array(
'user' => array(
'id' => $user->data->ID,
),
),
);
/** Let the user modify the token data before the sign. */
$token = JWT::encode(apply_filters('jwt_auth_token_before_sign', $token, $user), $secret_key);
/** The token is signed, now create the object with no sensible user data to the client*/
$data = array(
'token' => $token,
'user_email' => $user->data->user_email,
'user_nicename' => $user->data->user_nicename,
'user_display_name' => $user->data->display_name,
);
/** Let the user modify the data before send it back */
return apply_filters('jwt_auth_token_before_dispatch', $data, $user);
}
/**
* This is our Middleware to try to authenticate the user according to the
* token send.
*
* @param (int|bool) $user Logged User ID
*
* @return (int|bool)
*/
public function determine_current_user($user)
{
/**
* This hook only should run on the REST API requests to determine
* if the user in the Token (if any) is valid, for any other
* normal call ex. wp-admin/.* return the user.
*
* @since 1.2.3
**/
$rest_api_slug = rest_get_url_prefix();
$valid_api_uri = strpos($_SERVER['REQUEST_URI'], $rest_api_slug);
if (!$valid_api_uri) {
return $user;
}
/*
* if the request URI is for validate the token don't do anything,
* this avoid double calls to the validate_token function.
*/
$validate_uri = strpos($_SERVER['REQUEST_URI'], 'token/validate');
if ($validate_uri > 0) {
return $user;
}
$token = $this->validate_token(false);
if (is_wp_error($token)) {
if ($token->get_error_code() != 'jwt_auth_no_auth_header') {
/** If there is a error, store it to show it after see rest_pre_dispatch */
$this->jwt_error = $token;
return $user;
} else {
return $user;
}
}
/** Everything is ok, return the user ID stored in the token*/
return $token->data->user->id;
}
/**
* Main validation function, this function try to get the Autentication
* headers and decoded.
*
* @param bool $output
*
* @return WP_Error | Object | Array
*/
public function validate_token($output = true)
{
/*
* Looking for the HTTP_AUTHORIZATION header, if not present just
* return the user.
*/
$auth = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : false;
/* Double check for different auth header string (server dependent) */
if (!$auth) {
$auth = isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) ? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] : false;
}
if (!$auth) {
return new WP_Error(
'jwt_auth_no_auth_header',
'Authorization header not found.',
array(
'status' => 403,
)
);
}
/*
* The HTTP_AUTHORIZATION is present verify the format
* if the format is wrong return the user.
*/
list($token) = sscanf($auth, 'Bearer %s');
if (!$token) {
// Get token using basic auth
list($username, $password) = explode( ':', base64_decode( substr( $auth, 6 ) ) );
$request = new WP_REST_Request( 'POST', '/wp-json/jwt-auth/v1/token' );
$request->set_param( 'username', $username );
$request->set_param( 'password', $password );
$JWT = new Jwt_Auth_Public('jwt-auth', '1.1.0');
$token = $JWT->generate_token( $request );
if (is_array($token) && isset($token['token'])) $token = $token['token'];
return;
}
if (!$token) {
return new WP_Error(
'jwt_auth_bad_auth_header',
'Authorization header malformed.',
array(
'status' => 403,
)
);
}
/** Get the Secret Key */
$secret_key = defined('JWT_AUTH_SECRET_KEY') ? JWT_AUTH_SECRET_KEY : false;
if (!$secret_key) {
return new WP_Error(
'jwt_auth_bad_config',
'JWT is not configurated properly, please contact the admin',
array(
'status' => 403,
)
);
}
/** Try to decode the token */
try {
$token = JWT::decode($token, $secret_key, array('HS256'));
/** The Token is decoded now validate the iss */
if ($token->iss != get_bloginfo('url')) {
/** The iss do not match, return error */
return new WP_Error(
'jwt_auth_bad_iss',
'The iss do not match with this server',
array(
'status' => 403,
)
);
}
/** So far so good, validate the user id in the token */
if (!isset($token->data->user->id)) {
/** No user id in the token, abort!! */
return new WP_Error(
'jwt_auth_bad_request',
'User ID not found in the token',
array(
'status' => 403,
)
);
}
/** Everything looks good return the decoded token if the $output is false */
if (!$output) {
return $token;
}
/** If the output is true return an answer to the request to show it */
return array(
'code' => 'jwt_auth_valid_token',
'data' => array(
'status' => 200,
),
);
} catch (Exception $e) {
/** Something is wrong trying to decode the token, send back the error */
return new WP_Error(
'jwt_auth_invalid_token',
$e->getMessage(),
array(
'status' => 403,
)
);
}
}
/**
* Filter to hook the rest_pre_dispatch, if the is an error in the request
* send it, if there is no error just continue with the current request.
*
* @param $request
*/
public function rest_pre_dispatch($request)
{
if (is_wp_error($this->jwt_error)) {
return $this->jwt_error;
}
return $request;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment