diff --git a/.gitignore b/.gitignore
index 59ac2a2..174a021 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/.vscode
+/.idea
/node_modules
/vendor
/.phpunit.result.cache
diff --git a/README.md b/README.md
index 0934e5f..586fa94 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,7 @@ You can use the optional parameter `device` with the device identifier to let us
"message": "Credential is valid",
"data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvcG9pbnRzLmNvdXZlZS5jby5pZCIsImlhdCI6MTU4ODQ5OTE0OSwibmJmIjoxNTg4NDk5MTQ5LCJleHAiOjE1ODkxMDM5NDksImRhdGEiOnsidXNlciI6eyJpZCI6MX19fQ.w3pf5PslhviHohmiGF-JlPZV00XWE9c2MfvBK7Su9Fw",
+ "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvcG9pbnRzLmNvdXZlZS5jby5pZCIsImlhdCI6MTU4ODQ5OTE0OSwibmJmIjoxNTg4NDk5MTQ5LCJleHAiOjE1ODkxMDM5NDksImRhdGEiOnsidXNlciI6eyJpZCI6MX19fQ.w3pf5PslhviHohmiGF-JlPZV00XWE9c2MfvBK7Su9Fw",
"id": 1,
"email": "contactjavas@gmail.com",
"nicename": "contactjavas",
@@ -217,10 +218,24 @@ This means that a refresh token cannot be shared. To allow multiple devices to a
curl -F device="abc-def" -F username=myuser -F password=mypass /wp-json/jwt-auth/v1/token
```
```sh
-curl -F device="abc-def" -b "refresh_token=123.abcdef..." /wp-json/jwt-auth/v1/token
+# For a cookie flow
+curl -F device="abc-def" -b "refresh_token=eyJ0eXAiOi..." /wp-json/jwt-auth/v1/token
+
+# For a body flow
+curl -F device="abc-def" -d "refresh_token=eyJ0eXAiOi..." /wp-json/jwt-auth/v1/token
+
+# For a parameter flow
+curl -F device="abc-def" "/wp-json/jwt-auth/v1/token?refresh_token=eyJ0eXAiOi..."
```
```sh
-curl -F device="abc-def" -b "refresh_token=123.abcdef..." /wp-json/jwt-auth/v1/token/refresh
+# For a cookie flow
+curl -F device="abc-def" -b "refresh_token=eyJ0eXAiOi..." /wp-json/jwt-auth/v1/token/refresh
+
+# For a body flow
+curl -F device="abc-def" -d "refresh_token=eyJ0eXAiOi..." /wp-json/jwt-auth/v1/token/refresh
+
+# For a parameter flow
+curl -F device="abc-def" "/wp-json/jwt-auth/v1/token/refresh?refresh_token=eyJ0eXAiOi..."
```
@@ -331,7 +346,17 @@ If the token is invalid an error will be returned. Here are some samples of erro
"success": false,
"statusCode": 401,
"code": "jwt_auth_invalid_refresh_token",
- "message": "Invalid refresh token",
+ "message": "Device not found in the refresh token.",
+ "data": []
+}
+```
+
+```json
+{
+ "success": false,
+ "statusCode": 401,
+ "code": "jwt_auth_invalid_refresh_token",
+ "message": "Invalid token type",
"data": []
}
```
@@ -393,6 +418,36 @@ add_filter(
```
+### jwt_auth_flow
+
+The **jwt_auth_flow** allows you to decide which flow use for current request.
+
+The supported options are:
+- cookie __*(default)*__
+- body
+- query
+- header
+
+To enable the desired refresh token flow add an hook to your theme's functions.php file.
+```php
+/**
+ * Change the flow for refresh token.
+ *
+ * @param string $flow The current flow.
+ */
+add_filter(
+ 'jwt_auth_flow',
+ function ( $headers ) {
+ if (wp_doing_ajax()) {
+ // Modify the flow here.
+ return 'body';
+ }
+ return $flow;
+);
+```
+
+This value will be used to establish from with part of the request the refresh token will be taken.
+
### jwt_auth_authorization_header
The **jwt_auth_authorization_header** allows you to modify the Authorization header key used to validating a token. Useful when the server already uses the 'Authorization' key for another auth method.
@@ -455,6 +510,8 @@ add_filter(
### jwt_auth_not_before
+#### alias for [jwt_auth_toke_not_before](#jwt_auth_token_not_before)
+
The `jwt_auth_not_before` allows you to change the [**nbf**](https://tools.ietf.org/html/rfc7519#section-4.1.5) value before the payload is encoded to be a token
Default Value:
@@ -486,8 +543,43 @@ add_filter(
);
```
+### jwt_auth_token_not_before
+
+The `jwt_auth_token_not_before` allows you to change the [**nbf**](https://tools.ietf.org/html/rfc7519#section-4.1.5) value before the payload is encoded to be a token
+
+Default Value:
+
+```
+// Creation time.
+time()
+```
+
+Usage example:
+
+```php
+/**
+ * Change the token's nbf value.
+ *
+ * @param int $not_before The default "nbf" value in timestamp.
+ * @param int $issued_at The "iat" value in timestamp.
+ *
+ * @return int The "nbf" value.
+ */
+add_filter(
+ 'jwt_auth_token_not_before',
+ function ( $not_before, $issued_at ) {
+ // Modify the "not_before" here.
+ return $not_before;
+ },
+ 10,
+ 2
+);
+```
+
### jwt_auth_expire
+#### alias for [jwt_auth_token_expire](#jwt_auth_token_expire)
+
The `jwt_auth_expire` allows you to change the [**exp**](https://tools.ietf.org/html/rfc7519#section-4.1.4) value before the payload is encoded to be a token
Default Value:
@@ -518,9 +610,77 @@ add_filter(
);
```
+
+### jwt_auth_token_expire
+
+The `jwt_auth_token_expire` allows you to change the [**exp**](https://tools.ietf.org/html/rfc7519#section-4.1.4) value before the payload is encoded to be a token
+
+Default Value:
+
+```
+time() + (MINUTE_IN_SECONDS * 10)
+```
+
+Usage example:
+
+```php
+/**
+ * Change the token's expire value.
+ *
+ * @param int $expire The default "exp" value in timestamp.
+ * @param int $issued_at The "iat" value in timestamp.
+ *
+ * @return int The "nbf" value.
+ */
+add_filter(
+ 'jwt_auth_token_expire',
+ function ( $expire, $issued_at ) {
+ // Modify the "expire" here.
+ return $expire;
+ },
+ 10,
+ 2
+);
+```
+
+
+
+### jwt_auth_refresh_not_before
+
+The `jwt_auth_refresh_not_before` allows you to change the [**nbf**](https://tools.ietf.org/html/rfc7519#section-4.1.5) value before the payload is encoded to be a refresh token
+
+Default Value:
+
+```
+// Creation time.
+time()
+```
+
+Usage example:
+
+```php
+/**
+ * Change the refresh token's nbf value.
+ *
+ * @param int $not_before The default "nbf" value in timestamp.
+ * @param int $issued_at The "iat" value in timestamp.
+ *
+ * @return int The "nbf" value.
+ */
+add_filter(
+ 'jwt_auth_refresh_not_before',
+ function ( $not_before, $issued_at ) {
+ // Modify the "not_before" here.
+ return $not_before;
+ },
+ 10,
+ 2
+);
+```
+
### jwt_auth_refresh_expire
-The `jwt_auth_refresh_expire` filter hook allows you to change the expiration date of the refresh token.
+The `jwt_auth_refresh_expire` filter hook allows you to change the [**exp**](https://tools.ietf.org/html/rfc7519#section-4.1.4) value before the payload is encoded to be a refresh token
Default Value:
@@ -750,15 +910,15 @@ add_filter(
There are end-to-end tests you can run to confirm that the API works correctly:
```console
-$ URL=https://example.local USERNAME=myuser PASSWORD=mypass composer run test
+$ URL=https://example.local USERNAME=myuser PASSWORD=mypass FLOW=cookie composer run test
> ./vendor/bin/phpunit
-PHPUnit 9.5.13 by Sebastian Bergmann and contributors.
+PHPUnit 9.5.25 #StandWithUkraine
-............. 13 / 13 (100%)
+............... 15 / 15 (100%)
-Time: 00:12.377, Memory: 6.00 MB
+Time: 00:48.086, Memory: 8.00 MB
-OK (13 tests, 110 assertions)
+OK (15 tests, 143 assertions)
```
diff --git a/class-auth.php b/class-auth.php
index 204f3e1..2043f6d 100644
--- a/class-auth.php
+++ b/class-auth.php
@@ -8,7 +8,6 @@
namespace JWTAuth;
use Exception;
-
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
@@ -178,17 +177,20 @@ public function get_token( WP_REST_Request $request ) {
);
}
- if ( isset( $_COOKIE['refresh_token'] ) ) {
- $device = $request->get_param( 'device' ) ?: '';
- $user_id = $this->validate_refresh_token( $_COOKIE['refresh_token'], $device );
+ $refresh_token = $this->retrieve_refresh_token( $request );
+
+ if ( ! empty( $refresh_token ) ) {
+ $payload = $this->validate_refresh_token( $refresh_token, false );
// If we receive a REST response, then validation failed.
- if ( $user_id instanceof WP_REST_Response ) {
- return $user_id;
+ if ( $payload instanceof WP_REST_Response ) {
+ return $payload;
}
- $user = get_user_by( 'id', $user_id );
+ $user = get_user_by( 'id', $payload->data->user->id );
+ $device = $payload->data->device;
} else {
- $user = $this->authenticate_user( $username, $password, $custom_auth );
+ $user = $this->authenticate_user( $username, $password, $custom_auth );
+ $device = $request->get_param( 'device' ) ? $request->get_param( 'device' ) : '';
}
// If the authentication is failed return error response.
@@ -212,9 +214,11 @@ public function get_token( WP_REST_Request $request ) {
// Add the refresh token as a HttpOnly cookie to the response.
if ( $username && $password ) {
- $this->send_refresh_token( $user, $request );
+ $refresh_token = $this->send_refresh_token( $user, $device );
}
+ $response['data']['refresh_token'] = $refresh_token;
+
return $response;
}
@@ -231,10 +235,13 @@ public function generate_token( $user, $return_raw = true ) {
$issued_at = time();
$not_before = $issued_at;
$not_before = apply_filters( 'jwt_auth_not_before', $not_before, $issued_at );
+ $not_before = apply_filters( 'jwt_auth_token_not_before', $not_before, $issued_at );
$expire = $issued_at + ( MINUTE_IN_SECONDS * 10 );
$expire = apply_filters( 'jwt_auth_expire', $expire, $issued_at );
+ $expire = apply_filters( 'jwt_auth_token_expire', $expire, $issued_at );
$payload = array(
+ 'typ' => 'access',
'iss' => $this->get_iss(),
'iat' => $issued_at,
'nbf' => $not_before,
@@ -248,8 +255,13 @@ public function generate_token( $user, $return_raw = true ) {
$alg = $this->get_alg();
+ $payload = apply_filters( 'jwt_auth_payload', $payload, $user );
+
+ // Make sure to not lose type property
+ $payload['typ'] = 'access';
+
// Let the user modify the token data before the sign.
- $token = JWT::encode( apply_filters( 'jwt_auth_payload', $payload, $user ), $secret_key, $alg );
+ $token = JWT::encode( $payload, $secret_key, $alg );
// If return as raw token string.
if ( $return_raw ) {
@@ -281,17 +293,22 @@ public function generate_token( $user, $return_raw = true ) {
* Sends a new refresh token.
*
* @param \WP_User $user The WP_User object.
- * @param \WP_REST_Request $request The request.
+ * @param string $device Device name. Default empty string
*
- * @return void
+ * @return string
*/
- public function send_refresh_token( \WP_User $user, \WP_REST_Request $request ) {
- $refresh_token = bin2hex( random_bytes( 32 ) );
- $created = time();
- $expires = $created + DAY_IN_SECONDS * 30;
- $expires = apply_filters( 'jwt_auth_refresh_expire', $expires, $created );
+ public function send_refresh_token( \WP_User $user, string $device = '' ): string {
+ $secret_key = defined( 'JWT_AUTH_SECRET_KEY' ) ? JWT_AUTH_SECRET_KEY : false;
+ $refresh_token = $this->generate_refresh_token( $user, $device );
+
+ $alg = $this->get_alg();
- setcookie( 'refresh_token', $user->ID . '.' . $refresh_token, $expires, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
+ $payload = JWT::decode( $refresh_token, new Key( $secret_key, $alg ) );
+
+ if ( $this->is_flow( 'cookie' ) ) {
+ // Send the refresh token as a HttpOnly cookie in the response.
+ setcookie( 'refresh_token', $refresh_token, $payload->exp, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
+ }
// Save new refresh token for the user, replacing the previous one.
// The refresh token is rotated for the passed device only, not affecting
@@ -300,21 +317,83 @@ public function send_refresh_token( \WP_User $user, \WP_REST_Request $request )
if ( ! is_array( $user_refresh_tokens ) ) {
$user_refresh_tokens = array();
}
- $device = $request->get_param( 'device' ) ?: '';
+
$user_refresh_tokens[ $device ] = array(
'token' => $refresh_token,
- 'expires' => $expires,
+ 'expires' => $payload->exp,
);
update_user_meta( $user->ID, 'jwt_auth_refresh_tokens', $user_refresh_tokens );
// Store next expiry for cron_purge_expired_refresh_tokens event.
- $expires_next = $expires;
- foreach ( $user_refresh_tokens as $device ) {
- if ( $device['expires'] < $expires_next ) {
- $expires_next = $device['expires'];
+ $expires_next = $payload->exp;
+ foreach ( $user_refresh_tokens as $user_refresh_token ) {
+ if ( $user_refresh_token['expires'] < $expires_next ) {
+ $expires_next = $user_refresh_token['expires'];
}
}
update_user_meta( $user->ID, 'jwt_auth_refresh_tokens_expires_next', $expires_next );
+
+ return $refresh_token;
+ }
+
+ /**
+ * Generate a new refresh token.
+ *
+ * @param \WP_User $user The WP_User object.
+ * @param string $device Device name. Default empty string
+ *
+ * @return string
+ */
+ public function generate_refresh_token( \WP_User $user, string $device = '' ): string {
+ $secret_key = defined( 'JWT_AUTH_SECRET_KEY' ) ? JWT_AUTH_SECRET_KEY : false;
+ $issued_at = time();
+ $not_before = $issued_at;
+ $not_before = apply_filters( 'jwt_auth_refresh_not_before', $not_before, $issued_at );
+ $expires = $issued_at + DAY_IN_SECONDS * 30;
+ $expires = apply_filters( 'jwt_auth_refresh_expire', $expires, $issued_at );
+
+ $payload = array(
+ 'typ' => 'refresh',
+ 'iss' => $this->get_iss(),
+ 'iat' => $issued_at,
+ 'nbf' => $not_before,
+ 'exp' => $expires,
+ 'data' => array(
+ 'device' => $device,
+ 'user' => array(
+ 'id' => $user->ID,
+ ),
+ ),
+ );
+
+ $alg = $this->get_alg();
+
+ $payload = apply_filters( 'jwt_auth_refresh_token_payload', $payload, $user );
+
+ // Make sure to not lose type property
+ $payload['typ'] = 'refresh';
+
+ return JWT::encode( $payload, $secret_key, $alg );
+ }
+
+ /**
+ * Get the refresh token flow
+ *
+ * @return mixed|null
+ */
+ public function get_flow() {
+ return apply_filters( 'jwt_auth_flow', 'cookie' );
+ }
+
+ /**
+ * Check if the current flow is one of the given flows.
+ *
+ * @param string ...$desired The desired flows.
+ *
+ * @return bool
+ */
+ public function is_flow(...$desired) {
+ return in_array($this->get_flow(), $desired, true);
}
/**
@@ -356,21 +435,23 @@ public function is_error_response( $response ) {
/**
* Public token validation function based on Authorization header.
*
- * @param bool $return_response Either to return full WP_REST_Response or to return the payload only.
+ * @param bool|WP_REST_Request $return_response Either to return full WP_REST_Response or to return the payload only.
*
- * @return WP_REST_Response | Array Returns WP_REST_Response or token's $payload.
+ * @return \stdClass|WP_REST_Response Returns WP_REST_Response or token's $payload.
*/
- public function validate_token( $return_response = true ) {
+ public function validate_token( $return_response_or_request = true ) {
+ $return_response = $return_response_or_request instanceof WP_REST_Request ? true : $return_response_or_request;
+
/**
* Looking for the HTTP_AUTHORIZATION header, if not present just
* return the user.
*/
$headerkey = apply_filters( 'jwt_auth_authorization_header', 'HTTP_AUTHORIZATION' );
- $auth = isset( $_SERVER[ $headerkey ] ) ? sanitize_text_field( wp_unslash( $_SERVER[ $headerkey ] ) ) : false;
+ $auth = empty( $_SERVER[ $headerkey ] ) ? false : sanitize_text_field( wp_unslash( $_SERVER[ $headerkey ] ) );
// Double check for different auth header string (server dependent).
if ( ! $auth ) {
- $auth = isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) : false;
+ $auth = empty( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ? false : sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
}
if ( ! $auth ) {
@@ -441,6 +522,10 @@ public function validate_token( $return_response = true ) {
);
}
+ if ( ! isset( $payload->typ ) || $payload->typ !== 'access' ) {
+ throw new Exception( __( 'Invalid token type', 'jwt-auth' ) );
+ }
+
// Check the user id existence in the token.
if ( ! isset( $payload->data->user->id ) ) {
// No user id in the token, abort!!
@@ -528,28 +613,41 @@ public function validate_token( $return_response = true ) {
* @param WP_REST_Request $request The request.
* @return WP_REST_Response Returns WP_REST_Response.
*/
- public function refresh_token( WP_REST_Request $request ) {
- if ( ! isset( $_COOKIE['refresh_token'] ) ) {
+ public function refresh_token( \WP_REST_Request $request ) {
+
+ $input_refresh_token = $this->retrieve_refresh_token( $request );
+
+ if ( empty( $input_refresh_token ) ) {
return new WP_REST_Response(
array(
'success' => false,
'statusCode' => 401,
- 'code' => 'jwt_auth_no_auth_cookie',
- 'message' => __( 'Refresh token cookie not found.', 'jwt-auth' ),
+ 'code' => 'jwt_auth_no_refresh_token',
+ 'message' => __( 'Refresh token not found.', 'jwt-auth' ),
),
401
);
}
- $device = $request->get_param( 'device' ) ?: '';
- $user_id = $this->validate_refresh_token( $_COOKIE['refresh_token'], $device );
- if ( $user_id instanceof WP_REST_Response ) {
- return $user_id;
+ $payload = $this->validate_refresh_token( $input_refresh_token, false );
+ if ( $payload instanceof WP_REST_Response ) {
+ return $payload;
}
// Generate a new access token.
- $user = get_user_by( 'id', $user_id );
- $this->send_refresh_token( $user, $request );
+ $user = get_user_by( 'id', $payload->data->user->id );
+ $device = $payload->data->device;
+ $refresh_token = $this->send_refresh_token( $user, $device );
+
+ $additional_fields = array();
+
+ if ( ! $this->is_flow( 'cookie' ) ) {
+ $additional_fields = array(
+ 'data' => array(
+ 'refresh_token' => $refresh_token,
+ ),
+ );
+ }
$response = array(
'success' => true,
@@ -557,69 +655,188 @@ public function refresh_token( WP_REST_Request $request ) {
'code' => 'jwt_auth_valid_token',
'message' => __( 'Token is valid', 'jwt-auth' ),
);
- return new WP_REST_Response( $response );
+
+ return new WP_REST_Response( array_merge( $response, $additional_fields ) );
}
/**
* Validates refresh token.
*
- * @param string $refresh_token_cookie The refresh token to validate.
- * @param string $device The device of the refresh token.
- * @return int|WP_REST_Response Returns user ID if valid or WP_REST_Response on error.
+ * @param string $refresh_token The refresh token.
+ * @param bool $return_response Either to return full WP_REST_Response or to return the payload only.
+ *
+ * @return \stdClass|WP_REST_Response Returns user ID if valid or WP_REST_Response on error.
*/
- public function validate_refresh_token( $refresh_token_cookie, $device ) {
- $parts = explode( '.', $refresh_token_cookie );
- if ( count( $parts ) !== 2 || empty( intval( $parts[0] ) ) || empty( $parts[1] ) ) {
+ public function validate_refresh_token( $refresh_token, $return_response = true ) {
+
+ // Get the Secret Key.
+ $secret_key = defined( 'JWT_AUTH_SECRET_KEY' ) ? JWT_AUTH_SECRET_KEY : false;
+
+ if ( ! $secret_key ) {
return new WP_REST_Response(
array(
'success' => false,
'statusCode' => 401,
- 'code' => 'jwt_auth_invalid_refresh_token',
- 'message' => __( 'Invalid refresh token', 'jwt-auth' ),
+ 'code' => 'jwt_auth_bad_config',
+ 'message' => __( 'JWT is not configured properly.', 'jwt-auth' ),
+ 'data' => array(),
),
401
);
}
- // The refresh token must match the last issued refresh token for the passed
- // device.
- $user_id = intval( $parts[0] );
- $user_refresh_tokens = get_user_meta( $user_id, 'jwt_auth_refresh_tokens', true );
- $refresh_token = $parts[1];
+ // Try to decode the token.
+ try {
+ $alg = $this->get_alg();
+ $payload = JWT::decode( $refresh_token, new Key( $secret_key, $alg ) );
- if ( empty( $user_refresh_tokens[ $device ] ) ) {
- return new WP_REST_Response(
- array(
- 'success' => false,
- 'statusCode' => 401,
- 'code' => 'jwt_auth_invalid_refresh_token',
- 'message' => __( 'Invalid refresh token', 'jwt-auth' ),
- ),
- 401
- );
- } elseif ( $refresh_token !== $user_refresh_tokens[ $device ]['token'] ) {
- return new WP_REST_Response(
- array(
- 'success' => false,
- 'statusCode' => 401,
- 'code' => 'jwt_auth_obsolete_refresh_token',
- 'message' => __( 'Refresh token is obsolete', 'jwt-auth' ),
- ),
- 401
+ // The Token is decoded now validate the iss.
+ if ( $payload->iss !== $this->get_iss() ) {
+ // The iss do not match, return error.
+ return new WP_REST_Response(
+ array(
+ 'success' => false,
+ 'statusCode' => 401,
+ 'code' => 'jwt_auth_bad_iss',
+ 'message' => __( 'The iss do not match with this server.', 'jwt-auth' ),
+ 'data' => array(),
+ ),
+ 401
+ );
+ }
+
+ if ( ! isset( $payload->typ ) || $payload->typ !== 'refresh' ) {
+ throw new Exception( __( 'Invalid token type', 'jwt-auth' ) );
+ }
+
+ // Check the user id existence in the token.
+ if ( ! isset( $payload->data->user->id ) ) {
+ // No user id in the token, abort!!
+ return new WP_REST_Response(
+ array(
+ 'success' => false,
+ 'statusCode' => 401,
+ 'code' => 'jwt_auth_bad_request',
+ 'message' => __( 'User ID not found in the refresh token.', 'jwt-auth' ),
+ 'data' => array(),
+ ),
+ 401
+ );
+ }
+
+ // So far so good, check if the given user id exists in db.
+ $user = get_user_by( 'id', $payload->data->user->id );
+
+ if ( ! $user ) {
+ // No user id in the token, abort!!
+ return new WP_REST_Response(
+ array(
+ 'success' => false,
+ 'statusCode' => 401,
+ 'code' => 'jwt_auth_user_not_found',
+ 'message' => __( "User doesn't exist", 'jwt-auth' ),
+ 'data' => array(),
+ ),
+ 401
+ );
+ }
+
+ if ( ! isset( $payload->data->device ) ) {
+ // Throw invalid token response
+ throw new Exception( __( 'Device not found in the refresh token.', 'jwt-auth' ) );
+ }
+
+ // The refresh token must match the last issued refresh token for the passed
+ // device.
+ $user_id = $payload->data->user->id;
+ $user_refresh_tokens = get_user_meta( $user_id, 'jwt_auth_refresh_tokens', true );
+
+ if ( ! is_array( $user_refresh_tokens ) ) {
+ $user_refresh_tokens = array();
+ }
+
+ $device = empty( $payload->data->device ) ? '' : $payload->data->device;
+ $last_refresh_token_issued = $user_refresh_tokens[ $device ] ?? null;
+
+ if ( empty( $last_refresh_token_issued ) ) {
+ return new WP_REST_Response(
+ array(
+ 'success' => false,
+ 'statusCode' => 401,
+ 'code' => 'jwt_auth_invalid_refresh_token',
+ 'message' => __( 'Invalid refresh token', 'jwt-auth' ),
+ ),
+ 401
+ );
+ } elseif ( $refresh_token !== $last_refresh_token_issued['token'] ) {
+ return new WP_REST_Response(
+ array(
+ 'success' => false,
+ 'statusCode' => 401,
+ 'code' => 'jwt_auth_obsolete_refresh_token',
+ 'message' => __( 'Refresh token is obsolete', 'jwt-auth' ),
+ ),
+ 401
+ );
+ } elseif ( time() > $last_refresh_token_issued['expires'] ) {
+ return new WP_REST_Response(
+ array(
+ 'success' => false,
+ 'statusCode' => 401,
+ 'code' => 'jwt_auth_expired_refresh_token',
+ 'message' => __( 'Refresh token has expired', 'jwt-auth' ),
+ ),
+ 401
+ );
+ }
+
+ // Check extra condition if exists.
+ $failed_msg = apply_filters( 'jwt_auth_extra_refresh_token_check', '', $user, $refresh_token, $payload );
+
+ if ( ! empty( $failed_msg ) ) {
+ // No user id in the token, abort!!
+ return new WP_REST_Response(
+ array(
+ 'success' => false,
+ 'statusCode' => 401,
+ 'code' => 'jwt_auth_obsolete_token',
+ 'message' => __( 'Token is obsolete', 'jwt-auth' ),
+ 'data' => array(),
+ ),
+ 401
+ );
+ }
+
+ // Everything looks good, return the payload if $return_response is set to false.
+ if ( ! $return_response ) {
+ return $payload;
+ }
+
+ $response = array(
+ 'success' => true,
+ 'statusCode' => 200,
+ 'code' => 'jwt_auth_valid_token',
+ 'message' => __( 'Refresh token is valid', 'jwt-auth' ),
+ 'data' => array(),
);
- } elseif ( time() > $user_refresh_tokens[ $device ]['expires'] ) {
+
+ $response = apply_filters( 'jwt_auth_valid_refresh_token_response', $response, $user, $refresh_token, $payload );
+
+ // Otherwise, return success response.
+ return new WP_REST_Response( $response );
+ } catch ( Exception $e ) {
+ // Something is wrong when trying to decode the token, return error response.
return new WP_REST_Response(
array(
'success' => false,
'statusCode' => 401,
- 'code' => 'jwt_auth_expired_refresh_token',
- 'message' => __( 'Refresh token has expired', 'jwt-auth' ),
+ 'code' => 'jwt_auth_invalid_refresh_token',
+ 'message' => $e->getMessage(),
+ 'data' => array(),
),
401
);
}
-
- return $user_id;
}
/**
@@ -690,4 +907,30 @@ public function rest_pre_dispatch( $result, WP_REST_Server $server, WP_REST_Requ
return $result;
}
+
+ /**
+ * Retrieves the refresh token based on a flow
+ *
+ * @param WP_REST_Request $request
+ *
+ * @return string|null
+ */
+ private function retrieve_refresh_token( WP_REST_Request $request ): ?string {
+ $flow = $this->get_flow();
+
+ if ( 'body' === $flow ) {
+ $_array = $request->get_json_params();
+ } else if ( 'query' === $flow ) {
+ $_array = $request->get_query_params();
+ } else if ( 'header' === $flow ) {
+ $_array = $request->get_headers();
+ } else { // default cookie
+ $_array = $_COOKIE;
+ }
+
+ $refresh_token = $_array['refresh_token'] ?? null;
+
+ return apply_filters( 'jwt_auth_retrieve_refresh_token', $refresh_token, $flow );
+ }
+
}
diff --git a/composer.lock b/composer.lock
index f5d87fb..fb88f00 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "9cbcd0e3f958a25fe379a4c9f9e226e0",
+ "content-hash": "36baf9ffc0a30ae565209d739dc65a2c",
"packages": [
{
"name": "collizo4sky/persist-admin-notices-dismissal",
@@ -41,34 +41,39 @@
}
],
"description": "Simple library to persist dismissal of admin notices across pages in WordPress dashboard.",
+ "support": {
+ "issues": "https://github.com/w3guy/persist-admin-notices-dismissal/issues",
+ "source": "https://github.com/w3guy/persist-admin-notices-dismissal"
+ },
"time": "2024-03-10T15:11:42+00:00"
},
{
"name": "firebase/php-jwt",
- "version": "v6.3.2",
+ "version": "v6.10.0",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
- "reference": "ea7dda77098b96e666c5ef382452f94841e439cd"
+ "reference": "a49db6f0a5033aef5143295342f1c95521b075ff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/firebase/php-jwt/zipball/ea7dda77098b96e666c5ef382452f94841e439cd",
- "reference": "ea7dda77098b96e666c5ef382452f94841e439cd",
+ "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff",
+ "reference": "a49db6f0a5033aef5143295342f1c95521b075ff",
"shasum": ""
},
"require": {
- "php": "^7.1||^8.0"
+ "php": "^7.4||^8.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.5||^7.4",
- "phpspec/prophecy-phpunit": "^1.1",
- "phpunit/phpunit": "^7.5||^9.5",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.5",
"psr/cache": "^1.0||^2.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"suggest": {
+ "ext-sodium": "Support EdDSA (Ed25519) signatures",
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"type": "library",
@@ -101,38 +106,88 @@
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
- "source": "https://github.com/firebase/php-jwt/tree/v6.3.2"
+ "source": "https://github.com/firebase/php-jwt/tree/v6.10.0"
+ },
+ "time": "2023-12-01T16:26:39+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
+ "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
},
- "time": "2022-12-19T17:10:46+00:00"
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/1.1.4"
+ },
+ "time": "2021-05-03T11:20:27+00:00"
}
],
"packages-dev": [
{
"name": "doctrine/instantiator",
- "version": "2.0.0",
+ "version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
- "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": ""
},
"require": {
- "php": "^8.1"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
- "doctrine/coding-standard": "^11",
+ "doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*",
"ext-phar": "*",
- "phpbench/phpbench": "^1.2",
- "phpstan/phpstan": "^1.9.4",
- "phpstan/phpstan-phpunit": "^1.3",
- "phpunit/phpunit": "^9.5.27",
- "vimeo/psalm": "^5.4"
+ "phpbench/phpbench": "^0.16 || ^1",
+ "phpstan/phpstan": "^1.4",
+ "phpstan/phpstan-phpunit": "^1",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.30 || ^5.4"
},
"type": "library",
"autoload": {
@@ -159,7 +214,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
- "source": "https://github.com/doctrine/instantiator/tree/2.0.0"
+ "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
},
"funding": [
{
@@ -175,26 +230,26 @@
"type": "tidelift"
}
],
- "time": "2022-12-30T00:23:10+00:00"
+ "time": "2022-12-30T00:15:36+00:00"
},
{
"name": "guzzlehttp/guzzle",
- "version": "7.8.1",
+ "version": "7.9.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
+ "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
- "reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
+ "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
"shasum": ""
},
"require": {
"ext-json": "*",
- "guzzlehttp/promises": "^1.5.3 || ^2.0.1",
- "guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
+ "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
+ "guzzlehttp/psr7": "^2.7.0",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
@@ -205,9 +260,9 @@
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
- "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
+ "guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1",
- "phpunit/phpunit": "^8.5.36 || ^9.6.15",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
@@ -285,7 +340,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/7.8.1"
+ "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
},
"funding": [
{
@@ -301,20 +356,20 @@
"type": "tidelift"
}
],
- "time": "2023-12-03T20:35:24+00:00"
+ "time": "2024-07-24T11:22:20+00:00"
},
{
"name": "guzzlehttp/promises",
- "version": "2.0.2",
+ "version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223"
+ "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223",
- "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+ "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
"shasum": ""
},
"require": {
@@ -322,7 +377,7 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "phpunit/phpunit": "^8.5.36 || ^9.6.15"
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"type": "library",
"extra": {
@@ -368,7 +423,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/2.0.2"
+ "source": "https://github.com/guzzle/promises/tree/2.0.3"
},
"funding": [
{
@@ -384,20 +439,20 @@
"type": "tidelift"
}
],
- "time": "2023-12-03T20:19:20+00:00"
+ "time": "2024-07-18T10:29:17+00:00"
},
{
"name": "guzzlehttp/psr7",
- "version": "2.6.2",
+ "version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221"
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221",
- "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"shasum": ""
},
"require": {
@@ -412,8 +467,8 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "http-interop/http-factory-tests": "^0.9",
- "phpunit/phpunit": "^8.5.36 || ^9.6.15"
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
@@ -484,7 +539,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.6.2"
+ "source": "https://github.com/guzzle/psr7/tree/2.7.0"
},
"funding": [
{
@@ -500,20 +555,20 @@
"type": "tidelift"
}
],
- "time": "2023-12-03T20:05:35+00:00"
+ "time": "2024-07-18T11:15:46+00:00"
},
{
"name": "myclabs/deep-copy",
- "version": "1.11.1",
+ "version": "1.12.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
+ "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
- "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
+ "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
"shasum": ""
},
"require": {
@@ -521,11 +576,12 @@
},
"conflict": {
"doctrine/collections": "<1.6.8",
- "doctrine/common": "<2.13.3 || >=3,<3.2.2"
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
@@ -551,7 +607,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
},
"funding": [
{
@@ -559,20 +615,20 @@
"type": "tidelift"
}
],
- "time": "2023-03-08T13:26:56+00:00"
+ "time": "2024-06-12T14:39:25+00:00"
},
{
"name": "nikic/php-parser",
- "version": "v5.0.0",
+ "version": "v5.1.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc"
+ "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
- "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
+ "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
"shasum": ""
},
"require": {
@@ -583,7 +639,7 @@
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ "phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
@@ -615,26 +671,27 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
},
- "time": "2024-01-07T17:17:35+00:00"
+ "time": "2024-07-01T20:03:41+00:00"
},
{
"name": "phar-io/manifest",
- "version": "2.0.3",
+ "version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
- "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
- "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
"shasum": ""
},
"require": {
"ext-dom": "*",
+ "ext-libxml": "*",
"ext-phar": "*",
"ext-xmlwriter": "*",
"phar-io/version": "^3.0.1",
@@ -675,9 +732,15 @@
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
- "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
},
- "time": "2021-07-20T11:28:43+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
},
{
"name": "phar-io/version",
@@ -732,16 +795,16 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.30",
+ "version": "9.2.31",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089"
+ "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089",
- "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965",
+ "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965",
"shasum": ""
},
"require": {
@@ -798,7 +861,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31"
},
"funding": [
{
@@ -806,7 +869,7 @@
"type": "github"
}
],
- "time": "2023-12-22T06:47:57+00:00"
+ "time": "2024-03-02T06:37:42+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -1051,45 +1114,45 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.6.16",
+ "version": "9.6.20",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f"
+ "reference": "49d7820565836236411f5dc002d16dd689cde42f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f",
- "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f",
+ "reference": "49d7820565836236411f5dc002d16dd689cde42f",
"shasum": ""
},
"require": {
- "doctrine/instantiator": "^1.3.1 || ^2",
+ "doctrine/instantiator": "^1.5.0 || ^2",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.10.1",
- "phar-io/manifest": "^2.0.3",
- "phar-io/version": "^3.0.2",
+ "myclabs/deep-copy": "^1.12.0",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
"php": ">=7.3",
- "phpunit/php-code-coverage": "^9.2.28",
- "phpunit/php-file-iterator": "^3.0.5",
+ "phpunit/php-code-coverage": "^9.2.31",
+ "phpunit/php-file-iterator": "^3.0.6",
"phpunit/php-invoker": "^3.1.1",
- "phpunit/php-text-template": "^2.0.3",
- "phpunit/php-timer": "^5.0.2",
- "sebastian/cli-parser": "^1.0.1",
- "sebastian/code-unit": "^1.0.6",
+ "phpunit/php-text-template": "^2.0.4",
+ "phpunit/php-timer": "^5.0.3",
+ "sebastian/cli-parser": "^1.0.2",
+ "sebastian/code-unit": "^1.0.8",
"sebastian/comparator": "^4.0.8",
- "sebastian/diff": "^4.0.3",
- "sebastian/environment": "^5.1.3",
- "sebastian/exporter": "^4.0.5",
- "sebastian/global-state": "^5.0.1",
- "sebastian/object-enumerator": "^4.0.3",
- "sebastian/resource-operations": "^3.0.3",
- "sebastian/type": "^3.2",
+ "sebastian/diff": "^4.0.6",
+ "sebastian/environment": "^5.1.5",
+ "sebastian/exporter": "^4.0.6",
+ "sebastian/global-state": "^5.0.7",
+ "sebastian/object-enumerator": "^4.0.4",
+ "sebastian/resource-operations": "^3.0.4",
+ "sebastian/type": "^3.2.1",
"sebastian/version": "^3.0.2"
},
"suggest": {
@@ -1134,7 +1197,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20"
},
"funding": [
{
@@ -1150,7 +1213,7 @@
"type": "tidelift"
}
],
- "time": "2024-01-19T07:03:14+00:00"
+ "time": "2024-07-10T11:45:39+00:00"
},
{
"name": "psr/http-client",
@@ -1206,20 +1269,20 @@
},
{
"name": "psr/http-factory",
- "version": "1.0.2",
+ "version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
- "reference": "e616d01114759c4c489f93b099585439f795fe35"
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
- "reference": "e616d01114759c4c489f93b099585439f795fe35",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
- "php": ">=7.0.0",
+ "php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
@@ -1243,7 +1306,7 @@
"homepage": "https://www.php-fig.org/"
}
],
- "description": "Common interfaces for PSR-7 HTTP message factories",
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
@@ -1255,9 +1318,9 @@
"response"
],
"support": {
- "source": "https://github.com/php-fig/http-factory/tree/1.0.2"
+ "source": "https://github.com/php-fig/http-factory"
},
- "time": "2023-04-10T20:10:41+00:00"
+ "time": "2024-04-15T12:06:14+00:00"
},
{
"name": "psr/http-message",
@@ -1358,16 +1421,16 @@
},
{
"name": "sebastian/cli-parser",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
- "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+ "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
- "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+ "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
"shasum": ""
},
"require": {
@@ -1402,7 +1465,7 @@
"homepage": "https://github.com/sebastianbergmann/cli-parser",
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
- "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
},
"funding": [
{
@@ -1410,7 +1473,7 @@
"type": "github"
}
],
- "time": "2020-09-28T06:08:49+00:00"
+ "time": "2024-03-02T06:27:43+00:00"
},
{
"name": "sebastian/code-unit",
@@ -1656,16 +1719,16 @@
},
{
"name": "sebastian/diff",
- "version": "4.0.5",
+ "version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131"
+ "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
- "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
+ "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
"shasum": ""
},
"require": {
@@ -1710,7 +1773,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
- "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5"
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
},
"funding": [
{
@@ -1718,7 +1781,7 @@
"type": "github"
}
],
- "time": "2023-05-07T05:35:17+00:00"
+ "time": "2024-03-02T06:30:58+00:00"
},
{
"name": "sebastian/environment",
@@ -1785,16 +1848,16 @@
},
{
"name": "sebastian/exporter",
- "version": "4.0.5",
+ "version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
+ "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
- "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72",
+ "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72",
"shasum": ""
},
"require": {
@@ -1850,7 +1913,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
- "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6"
},
"funding": [
{
@@ -1858,20 +1921,20 @@
"type": "github"
}
],
- "time": "2022-09-14T06:03:37+00:00"
+ "time": "2024-03-02T06:33:00+00:00"
},
{
"name": "sebastian/global-state",
- "version": "5.0.6",
+ "version": "5.0.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "bde739e7565280bda77be70044ac1047bc007e34"
+ "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34",
- "reference": "bde739e7565280bda77be70044ac1047bc007e34",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
+ "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
"shasum": ""
},
"require": {
@@ -1914,7 +1977,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
- "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7"
},
"funding": [
{
@@ -1922,7 +1985,7 @@
"type": "github"
}
],
- "time": "2023-08-02T09:26:13+00:00"
+ "time": "2024-03-02T06:35:11+00:00"
},
{
"name": "sebastian/lines-of-code",
@@ -2158,16 +2221,16 @@
},
{
"name": "sebastian/resource-operations",
- "version": "3.0.3",
+ "version": "3.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git",
- "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
+ "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
- "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
+ "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
"shasum": ""
},
"require": {
@@ -2179,7 +2242,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-main": "3.0-dev"
}
},
"autoload": {
@@ -2200,8 +2263,7 @@
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
- "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
- "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4"
},
"funding": [
{
@@ -2209,7 +2271,7 @@
"type": "github"
}
],
- "time": "2020-09-28T06:45:17+00:00"
+ "time": "2024-03-14T16:00:52+00:00"
},
{
"name": "sebastian/type",
@@ -2322,25 +2384,25 @@
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.4.0",
+ "version": "v2.5.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
+ "reference": "80d075412b557d41002320b96a096ca65aa2c98d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
- "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d",
+ "reference": "80d075412b557d41002320b96a096ca65aa2c98d",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.4-dev"
+ "dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -2369,7 +2431,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3"
},
"funding": [
{
@@ -2385,20 +2447,20 @@
"type": "tidelift"
}
],
- "time": "2023-05-23T14:45:45+00:00"
+ "time": "2023-01-24T14:02:46+00:00"
},
{
"name": "theseer/tokenizer",
- "version": "1.2.2",
+ "version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
- "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
- "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
"shasum": ""
},
"require": {
@@ -2427,7 +2489,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
- "source": "https://github.com/theseer/tokenizer/tree/1.2.2"
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
},
"funding": [
{
@@ -2435,7 +2497,7 @@
"type": "github"
}
],
- "time": "2023-11-20T00:12:19+00:00"
+ "time": "2024-03-03T12:36:25+00:00"
}
],
"aliases": [],
@@ -2449,5 +2511,5 @@
"ext-json": "*",
"ext-curl": "*"
},
- "plugin-api-version": "2.6.0"
+ "plugin-api-version": "2.2.0"
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 1f31dd4..b839643 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -15,6 +15,7 @@
+
diff --git a/readme.txt b/readme.txt
index a11918f..a07020c 100644
--- a/readme.txt
+++ b/readme.txt
@@ -344,6 +344,16 @@ If the token is invalid an error will be returned. Here are some samples of erro
}
`
+`
+{
+ "success": false,
+ "statusCode": 401,
+ "code": "jwt_auth_invalid_refresh_token",
+ "message": "Invalid token type",
+ "data": []
+}
+`
+
= Obsolete Refresh Token =
`
diff --git a/tests/src/AccessTokenTest.php b/tests/src/AccessTokenTest.php
index 73b3dd9..0973a23 100644
--- a/tests/src/AccessTokenTest.php
+++ b/tests/src/AccessTokenTest.php
@@ -14,118 +14,191 @@ final class AccessTokenTest extends TestCase {
* @throws GuzzleException
*/
public function testToken(): string {
- $response = $this->client->post('/wp-json/jwt-auth/v1/token', [
- 'form_params' => [
- 'username' => $this->username,
- 'password' => $this->password,
- ],
- ]);
- $body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_valid_credential', $body['code']);
- $this->assertEquals(200, $response->getStatusCode());
- $this->assertEquals(true, $body['success']);
-
- $this->assertArrayHasKey('data', $body);
- $this->assertArrayHasKey('token', $body['data']);
- $this->token = $body['data']['token'];
- $this->assertNotEmpty($this->token);
-
- $cookie = $this->cookies->getCookieByName('refresh_token');
- $this->refreshToken = $cookie->getValue();
- $this->assertNotEmpty($this->refreshToken);
- $this->assertNotEquals($this->token, $this->refreshToken);
-
- return $this->token;
- }
-
- /**
- * @depends testToken
- * @throws GuzzleException
- */
- public function testTokenValidate(string $token): void {
- $this->assertNotEmpty($token);
-
- $response = $this->client->post('/wp-json/jwt-auth/v1/token/validate', [
- 'headers' => [
- 'Authorization' => "Bearer $token",
- ],
- ]);
- $body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_valid_token', $body['code']);
- $this->assertEquals(200, $response->getStatusCode());
- $this->assertEquals(true, $body['success']);
- }
-
- /**
- * @depends testToken
- * @throws GuzzleException
- */
- public function testTokenValidateWithInvalidToken(string $token): void {
- $this->assertNotEmpty($token);
-
- $response = $this->client->post('/wp-json/jwt-auth/v1/token/validate', [
- 'headers' => [
- 'Authorization' => "Bearer {$token}123",
- ],
- ]);
- $body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_invalid_token', $body['code']);
- $this->assertEquals(401, $response->getStatusCode());
- $this->assertEquals(false, $body['success']);
- }
-
- /**
- * @depends testToken
- * @throws GuzzleException
- */
- public function testTokenRefreshWithInvalidToken(string $token): void {
- $this->assertNotEmpty($token);
-
- $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', [
- 'headers' => [
- 'Authorization' => "Bearer {$token}",
- ],
- ]);
- $body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_no_auth_cookie', $body['code']);
- $this->assertEquals(401, $response->getStatusCode());
- $this->assertEquals(false, $body['success']);
-
- $cookies = [
- 'refresh_token' => $token,
- ];
- $domain = $this->getDomain();
- $cookies = CookieJar::fromArray($cookies, $domain);
-
- $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', [
- 'cookies' => $cookies,
- ]);
- $body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_invalid_refresh_token', $body['code']);
- $this->assertEquals(401, $response->getStatusCode());
- $this->assertEquals(false, $body['success']);
- }
-
- /**
- * @depends testToken
- * @throws GuzzleException
- */
- public function testTokenWithInvalidRefreshToken(string $token): void {
- $this->assertNotEmpty($token);
-
- $cookies = [
- 'refresh_token' => $token,
- ];
- $domain = $this->getDomain();
- $cookies = CookieJar::fromArray($cookies, $domain);
-
- $response = $this->client->post('/wp-json/jwt-auth/v1/token', [
- 'cookies' => $cookies,
- ]);
- $body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_invalid_refresh_token', $body['code']);
- $this->assertEquals(401, $response->getStatusCode());
- $this->assertEquals(false, $body['success']);
- }
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token', [
+ 'form_params' => [
+ 'username' => $this->username,
+ 'password' => $this->password,
+ ],
+ ]);
+ $body = json_decode($response->getBody()->getContents(), true);
+ $this->assertEquals('jwt_auth_valid_credential', $body['code']);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals(true, $body['success']);
+
+ $this->assertArrayHasKey('data', $body);
+ $this->assertArrayHasKey('token', $body['data']);
+ $this->token = $body['data']['token'];
+ $this->assertNotEmpty( $this->token );
+
+ if ($this->flow === 'cookie') {
+ $cookie = $this->cookies->getCookieByName('refresh_token');
+ $this->refreshToken = $cookie->getValue();
+ } else {
+ $this->assertArrayHasKey('refresh_token', $body['data']);
+ $this->refreshToken = $body['data']['refresh_token'];
+ }
+
+ $this->assertNotEmpty($this->refreshToken);
+ $this->assertNotEquals($this->token, $this->refreshToken);
+
+ return $this->token;
+ }
+
+ /**
+ * @depends testToken
+ * @throws GuzzleException
+ */
+ public function testTokenWithEditedTokenType(string $token): void {
+ $this->assertNotEmpty($token);
+
+ $payload = json_decode(base64_decode(explode('.', $token)[1]), false);
+ $payload->typ = 'refresh';
+ $malicious_token = implode('.', [
+ explode('.', $token )[0],
+ base64_encode(json_encode($payload)),
+ explode('.', $token )[2],
+ ]);
+
+ $request_options = array();
+
+ if ($this->flow === 'cookie') {
+ $cookies = [
+ 'refresh_token' => $malicious_token,
+ ];
+ $domain = $this->getDomain();
+ $cookies = CookieJar::fromArray($cookies, $domain);
+ $request_options['cookies'] = $cookies;
+ } else if ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $token,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $token,
+ ];
+ }
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', $request_options);
+ $body = json_decode($response->getBody()->getContents(), true);
+ $this->assertIsArray($body);
+ $this->assertArrayHasKey('data', $body);
+ $this->assertEquals('jwt_auth_invalid_refresh_token', $body['code']);
+ $this->assertEquals(401, $response->getStatusCode());
+ $this->assertEquals(false, $body['success']);
+ }
+
+ /**
+ * @depends testToken
+ * @throws GuzzleException
+ */
+ public function testTokenValidate(string $token): void {
+ $this->assertNotEmpty($token);
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/validate', [
+ 'headers' => [
+ 'Authorization' => "Bearer $token",
+ ],
+ ]);
+ $body = json_decode($response->getBody()->getContents(), true);
+ $this->assertEquals('jwt_auth_valid_token', $body['code']);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals(true, $body['success']);
+ }
+
+ /**
+ * @depends testToken
+ * @throws GuzzleException
+ */
+ public function testTokenValidateWithInvalidToken(string $token): void {
+ $this->assertNotEmpty($token);
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/validate', [
+ 'headers' => [
+ 'Authorization' => "Bearer {$token}123",
+ ],
+ ]);
+ $body = json_decode($response->getBody()->getContents(), true);
+ $this->assertEquals('jwt_auth_invalid_token', $body['code']);
+ $this->assertEquals(401, $response->getStatusCode());
+ $this->assertEquals(false, $body['success']);
+ }
+
+ /**
+ * @depends testToken
+ * @throws GuzzleException
+ */
+ public function testTokenRefreshWithInvalidToken(string $token): void {
+ $this->assertNotEmpty($token);
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', [
+ 'headers' => [
+ 'Authorization' => "Bearer {$token}",
+ ],
+ ]);
+ $body = json_decode($response->getBody()->getContents(), true);
+ if ($this->flow === 'cookie') {
+ $this->assertEquals('jwt_auth_no_auth_cookie', $body['code']);
+ } else {
+ $this->assertEquals('jwt_auth_no_refresh_token', $body['code']);
+ }
+ $this->assertEquals(401, $response->getStatusCode());
+ $this->assertEquals(false, $body['success']);
+
+ $request_options = array();
+
+ if ($this->flow === 'cookie') {
+ $cookies = [
+ 'refresh_token' => $token,
+ ];
+ $domain = $this->getDomain();
+ $cookies = CookieJar::fromArray($cookies, $domain);
+ $request_options['cookies'] = $cookies;
+ } else if ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $token,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $token,
+ ];
+ }
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', $request_options);
+ $body = json_decode($response->getBody()->getContents(), true);
+ $this->assertEquals('jwt_auth_invalid_refresh_token', $body['code']);
+ $this->assertEquals(401, $response->getStatusCode());
+ $this->assertEquals(false, $body['success']);
+ }
+
+ /**
+ * @depends testToken
+ * @throws GuzzleException
+ */
+ public function testTokenWithInvalidRefreshToken(string $token): void {
+ $this->assertNotEmpty($token);
+
+ $request_options = array();
+
+ if ($this->flow === 'cookie') {
+ $cookies = [
+ 'refresh_token' => $token,
+ ];
+ $domain = $this->getDomain();
+ $cookies = CookieJar::fromArray( $cookies, $domain );
+ $request_options['cookies'] = $cookies;
+ } else if ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $token,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $token,
+ ];
+ }
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token', $request_options);
+ $body = json_decode($response->getBody()->getContents(), true);
+ $this->assertEquals('jwt_auth_invalid_refresh_token', $body['code']);
+ $this->assertEquals(401, $response->getStatusCode());
+ $this->assertEquals(false, $body['success']);
+ }
}
diff --git a/tests/src/RefreshTokenTest.php b/tests/src/RefreshTokenTest.php
index 04809a7..cd3bcf2 100644
--- a/tests/src/RefreshTokenTest.php
+++ b/tests/src/RefreshTokenTest.php
@@ -33,12 +33,18 @@ public function testToken(): string {
$this->token = $body['data']['token'];
$this->assertNotEmpty($this->token);
- // Discard the refresh_token cookie we set above to only retain the
- // refresh_token cookie from the response.
- $this->cookies->clearSessionCookies();
+ if ($this->flow === 'cookie') {
+ // Discard the refresh_token cookie we set above to only retain the
+ // refresh_token cookie from the response.
+ $this->cookies->clearSessionCookies();
+
+ $cookie = $this->cookies->getCookieByName('refresh_token');
+ $this->refreshToken = $cookie->getValue();
+ } else {
+ $this->assertArrayHasKey('refresh_token', $body['data']);
+ $this->refreshToken = $body['data']['refresh_token'];
+ }
- $cookie = $this->cookies->getCookieByName('refresh_token');
- $this->refreshToken = $cookie->getValue();
$this->assertNotEmpty($this->refreshToken);
$this->assertNotEquals($this->token, $this->refreshToken);
@@ -47,7 +53,35 @@ public function testToken(): string {
/**
* @depends testToken
- * @throws GuzzleException
+ */
+ public function testTokenWithEditedTokenType(string $refreshToken): void {
+ $this->assertNotEmpty($refreshToken);
+
+ $this->assertCount(3, explode('.', $refreshToken));
+
+ $payload = json_decode(base64_decode(explode('.', $refreshToken)[1]), false);
+ $payload->typ = 'access';
+ $malicious_refreshToken = implode('.', [
+ explode('.', $refreshToken)[0],
+ base64_encode(json_encode($payload)),
+ explode('.', $refreshToken)[2],
+ ]);
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/validate', [
+ 'headers' => [
+ 'Authorization' => "Bearer {$malicious_refreshToken}",
+ ],
+ ]);
+ $body = json_decode($response->getBody()->getContents(), true);
+ $this->assertIsArray($body);
+ $this->assertArrayHasKey('data', $body);
+ $this->assertEquals('jwt_auth_invalid_token', $body['code']);
+ $this->assertEquals(401, $response->getStatusCode());
+ $this->assertEquals(false, $body['success']);
+ }
+
+ /**
+ * @depends testToken
*/
public function testTokenValidateWithRefreshToken(string $refreshToken): void {
$this->assertNotEmpty($refreshToken);
@@ -58,6 +92,8 @@ public function testTokenValidateWithRefreshToken(string $refreshToken): void {
],
]);
$body = json_decode($response->getBody()->getContents(), true);
+ $this->assertIsArray($body);
+ $this->assertArrayHasKey('data', $body);
$this->assertEquals('jwt_auth_invalid_token', $body['code']);
$this->assertEquals(401, $response->getStatusCode());
$this->assertEquals(false, $body['success']);
@@ -70,15 +106,29 @@ public function testTokenValidateWithRefreshToken(string $refreshToken): void {
public function testTokenWithRefreshToken(string $refreshToken): void {
$this->assertNotEmpty($refreshToken);
- $cookies = [
- 'refresh_token' => $refreshToken,
- ];
- $domain = $this->getDomain();
- $cookies = CookieJar::fromArray($cookies, $domain);
+ $request_options = array();
+
+ if ($this->flow === 'cookie') {
+ $cookies = [
+ 'refresh_token' => $refreshToken,
+ ];
+ $domain = $this->getDomain();
+ $cookies = CookieJar::fromArray($cookies, $domain);
+
+ $request_options['cookies'] = $cookies;
+
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $refreshToken,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $refreshToken,
+ ];
+ }
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token', $request_options);
- $response = $this->client->post('/wp-json/jwt-auth/v1/token', [
- 'cookies' => $cookies,
- ]);
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_valid_credential', $body['code']);
$this->assertEquals(200, $response->getStatusCode());
@@ -90,12 +140,20 @@ public function testTokenWithRefreshToken(string $refreshToken): void {
$this->assertNotEmpty($this->token);
$this->assertNotEquals($this->token, $refreshToken);
- // Discard the refresh_token cookie we set above to only retain the
- // refresh_token cookie from the response.
- $cookies->clearSessionCookies();
+ if ($this->flow === 'cookie') {
+ // Discard the refresh_token cookie we set above to only retain the
+ // refresh_token cookie from the response.
+ $this->cookies->clearSessionCookies();
+
+ $cookie = $this->cookies->getCookieByName('refresh_token');
+ $this->refreshToken = $cookie->getValue();
+ } else {
+ $this->assertArrayHasKey('refresh_token', $body['data']);
+ $this->refreshToken = $body['data']['refresh_token'];
+ }
- $cookie = $cookies->getCookieByName('refresh_token');
- $this->assertEmpty($cookie);
+ $this->assertNotEmpty($this->refreshToken);
+ $this->assertNotEquals($this->token, $this->refreshToken);
}
/**
@@ -105,17 +163,30 @@ public function testTokenWithRefreshToken(string $refreshToken): void {
public function testTokenWithInvalidRefreshToken(string $refreshToken): void {
$this->assertNotEmpty($refreshToken);
- $cookies = [
- 'refresh_token' => $refreshToken . '123',
- ];
- $domain = $this->getDomain();
- $cookies = CookieJar::fromArray($cookies, $domain);
+ $request_options = array();
+
+ if ($this->flow === 'cookie') {
+
+ $cookies = [
+ 'refresh_token' => $refreshToken . '123',
+ ];
+ $domain = $this->getDomain();
+ $cookies = CookieJar::fromArray($cookies, $domain);
+
+ $request_options['cookies'] = $cookies;
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $refreshToken . '123',
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $refreshToken . '123',
+ ];
+ }
- $response = $this->client->post('/wp-json/jwt-auth/v1/token', [
- 'cookies' => $cookies,
- ]);
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token', $request_options);
$body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_obsolete_refresh_token', $body['code']);
+ $this->assertEquals('jwt_auth_invalid_refresh_token', $body['code']);
$this->assertEquals(401, $response->getStatusCode());
$this->assertEquals(false, $body['success']);
}
@@ -127,27 +198,50 @@ public function testTokenWithInvalidRefreshToken(string $refreshToken): void {
public function testTokenRefresh(string $refreshToken): string {
$this->assertNotEmpty($refreshToken);
- $cookies = [
- 'refresh_token' => $refreshToken,
- ];
- $domain = $this->getDomain();
- $cookies = CookieJar::fromArray($cookies, $domain);
+ // Wait 1 seconds as the token creation is based on timestamp in seconds.
+ sleep(1);
+
+ $request_options = array();
+
+ if ($this->flow === 'cookie') {
+ $cookies = [
+ 'refresh_token' => $refreshToken,
+ ];
+ $domain = $this->getDomain();
+ $cookies = CookieJar::fromArray($cookies, $domain);
+
+ $request_options['cookies'] = $cookies;
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $refreshToken,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $refreshToken,
+ ];
+ }
- $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', [
- 'cookies' => $cookies,
- ]);
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', $request_options);
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_valid_token', $body['code']);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(true, $body['success']);
- $this->assertArrayNotHasKey('data', $body);
- // Discard the refresh_token cookie we set above to only retain the
- // refresh_token cookie from the response.
- $cookies->clearSessionCookies();
+ if ($this->flow === 'cookie') {
+ $this->assertArrayNotHasKey('data', $body);
+
+ // Discard the refresh_token cookie we set above to only retain the
+ // refresh_token cookie from the response.
+ $cookies->clearSessionCookies();
+
+ $cookie = $cookies->getCookieByName('refresh_token');
+ $this->refreshToken = $cookie->getValue();
+ } else {
+ $this->assertArrayHasKey('data', $body);
+ $this->assertArrayHasKey('refresh_token', $body['data']);
+ $this->refreshToken = $body['data']['refresh_token'];
+ }
- $cookie = $cookies->getCookieByName('refresh_token');
- $this->refreshToken = $cookie->getValue();
$this->assertNotEmpty($this->refreshToken);
$this->assertNotEquals($this->refreshToken, $refreshToken);
@@ -163,33 +257,70 @@ public function testTokenWithRotatedRefreshToken(): void {
$refreshToken1 = $this->testToken();
$this->assertNotEmpty($refreshToken1);
- $domain = $this->getDomain();
+ // Wait 1 seconds as the token creation is based on timestamp in seconds.
+ sleep(1);
- // Fetch a new refresh token.
- $this->cookies->clear();
- $this->setCookie('refresh_token', $refreshToken1, $domain);
- $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh');
+ $request_options = array();
+
+ if ($this->flow === 'cookie') {
+ $domain = $this->getDomain();
+
+ // Fetch a new refresh token.
+ $this->cookies->clear();
+ $this->setCookie('refresh_token', $refreshToken1, $domain);
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $refreshToken1,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $refreshToken1,
+ ];
+ }
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', $request_options);
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_valid_token', $body['code']);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(true, $body['success']);
- $this->assertArrayNotHasKey('data', $body);
- // Discard the refresh_token cookie we set above to only retain the
- // refresh_token cookie from the response.
- $this->cookies->clearSessionCookies();
+ if ($this->flow === 'cookie') {
+ $this->assertArrayNotHasKey('data', $body);
+
+ // Discard the refresh_token cookie we set above to only retain the
+ // refresh_token cookie from the response.
+ $this->cookies->clearSessionCookies();
+
+ $cookie = $this->cookies->getCookieByName('refresh_token');
+ $refreshToken2 = $cookie->getValue();
- $cookie = $this->cookies->getCookieByName('refresh_token');
- $refreshToken2 = $cookie->getValue();
+ } else {
+ $this->assertArrayHasKey('data', $body);
+ $this->assertArrayHasKey('refresh_token', $body['data']);
+ $refreshToken2 = $body['data']['refresh_token'];
+ }
$this->assertNotEmpty($refreshToken2);
// Confirm the refresh token was rotated.
$this->assertNotEquals($refreshToken2, $refreshToken1);
- // Confirm the rotated refresh token is valid.
- $this->cookies->clear();
- $this->setCookie('refresh_token', $refreshToken2, $domain);
- $response = $this->client->post('/wp-json/jwt-auth/v1/token');
+ if ($this->flow === 'cookie') {
+ $domain = $this->getDomain();
+
+ // Confirm the rotated refresh token is valid.
+ $this->cookies->clear();
+ $this->setCookie('refresh_token', $refreshToken2, $domain);
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $refreshToken2,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $refreshToken2,
+ ];
+ }
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token', $request_options);
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_valid_credential', $body['code']);
$this->assertEquals(200, $response->getStatusCode());
@@ -201,19 +332,31 @@ public function testTokenWithRotatedRefreshToken(): void {
$this->assertNotEmpty($this->token);
$this->assertNotEquals($this->token, $refreshToken2);
- // Discard the refresh_token cookie we set above to only retain the
- // refresh_token cookie from the response.
- $this->cookies->clearSessionCookies();
+ if ($this->flow === 'cookie') {
+ $domain = $this->getDomain();
- $cookie = $this->cookies->getCookieByName('refresh_token');
- $this->assertEmpty($cookie);
+ // Discard the refresh_token cookie we set above to only retain the
+ // refresh_token cookie from the response.
+ $this->cookies->clearSessionCookies();
- // Confirm the previous refresh token is no longer valid.
- $this->cookies->clear();
- $this->setCookie('refresh_token', $refreshToken1, $domain);
- $response = $this->client->post('/wp-json/jwt-auth/v1/token');
+ $cookie = $this->cookies->getCookieByName('refresh_token');
+ $this->assertEmpty($cookie);
+
+ // Confirm the previous refresh token is no longer valid.
+ $this->cookies->clear();
+ $this->setCookie('refresh_token', $refreshToken1, $domain);
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $refreshToken1,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $refreshToken1,
+ ];
+ }
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token', $request_options);
$body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_obsolete_refresh_token', $body['code']);
+ $this->assertEquals('jwt_auth_obsolete_refresh_token', $body['code'], $body['message']);
$this->assertEquals(401, $response->getStatusCode());
$this->assertEquals(false, $body['success']);
}
@@ -241,13 +384,20 @@ public function testTokenRefreshRotationByDevice() {
'form_params' => [
'username' => $this->username,
'password' => $this->password,
- 'device' => $devices[$i]['device'],
+ 'device' => $devices[$i]['device'],
],
]);
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_valid_credential', $body['code']);
- $cookie = $this->cookies->getCookieByName('refresh_token');
- $devices[$i]['refresh_token'] = $cookie->getValue();
+
+ if ($this->flow === 'cookie') {
+ $cookie = $this->cookies->getCookieByName('refresh_token');
+ $devices[$i]['refresh_token'] = $cookie->getValue();
+ } else {
+ $this->assertArrayHasKey('data', $body);
+ $this->assertArrayHasKey('refresh_token', $body['data']);
+ $devices[$i]['refresh_token'] = $body['data']['refresh_token'];
+ }
$this->assertNotEmpty($devices[$i]['refresh_token']);
if (isset($devices[$i - 1]['refresh_token'])) {
@@ -257,24 +407,44 @@ public function testTokenRefreshRotationByDevice() {
$this->cookies->clear();
}
+ // Wait 1 seconds as the token creation is based on timestamp in seconds.
+ sleep(1);
+
// Refresh token with each device.
for ($i = 1; $i <= count($devices); $i++) {
$initial_refresh_token = $devices[$i]['refresh_token'];
- $this->setCookie('refresh_token', $devices[$i]['refresh_token'], $domain);
- $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', [
- 'form_params' => [
+ $request_options = array();
+ if ($this->flow === 'cookie') {
+ $request_options['form_params'] = [
'device' => $devices[$i]['device'],
- ],
- ]);
+ ];
+ $this->setCookie('refresh_token', $initial_refresh_token, $domain);
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $initial_refresh_token,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $initial_refresh_token,
+ ];
+ }
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', $request_options);
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_valid_token', $body['code']);
- // Discard the refresh_token cookie we set above to only retain the
- // refresh_token cookie from the response.
- $this->cookies->clearSessionCookies();
- $cookie = $this->cookies->getCookieByName('refresh_token');
- $devices[$i]['refresh_token'] = $cookie->getValue();
+ if ($this->flow === 'cookie') {
+ // Discard the refresh_token cookie we set above to only retain the
+ // refresh_token cookie from the response.
+ $this->cookies->clearSessionCookies();
+ $cookie = $this->cookies->getCookieByName('refresh_token');
+ $devices[$i]['refresh_token'] = $cookie->getValue();
+ } else {
+ $this->assertArrayHasKey('data', $body);
+ $this->assertArrayHasKey('refresh_token', $body['data']);
+ $devices[$i]['refresh_token'] = $body['data']['refresh_token'];
+ }
$this->assertNotEmpty($devices[$i]['refresh_token']);
$this->assertNotEquals($initial_refresh_token, $devices[$i]['refresh_token']);
@@ -287,26 +457,47 @@ public function testTokenRefreshRotationByDevice() {
// Confirm each device can use its refresh token to authenticate.
for ($i = 1; $i <= count($devices); $i++) {
- $this->setCookie('refresh_token', $devices[$i]['refresh_token'], $domain);
- $response = $this->client->post('/wp-json/jwt-auth/v1/token', [
- 'form_params' => [
- 'device' => $devices[$i]['device'],
- ],
- ]);
+
+ $request_options = array();
+ if ($this->flow === 'cookie') {
+ $this->setCookie('refresh_token', $devices[$i]['refresh_token'], $domain);
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $devices[$i]['refresh_token'],
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $devices[$i]['refresh_token'],
+ ];
+ }
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token', $request_options);
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_valid_credential', $body['code']);
+ $this->assertArrayHasKey('data', $body);
$this->assertArrayHasKey('token', $body['data']);
- $this->cookies->clear();
+ if ($this->flow === 'cookie') {
+ $this->cookies->clear();
+ } else {
+ $this->assertArrayHasKey('refresh_token', $body['data']);
+ }
}
+ $request_options = array();
// Confirm the previous refresh token is no longer valid.
- $this->setCookie('refresh_token', $initial_refresh_token, $domain);
- $response = $this->client->post('/wp-json/jwt-auth/v1/token', [
- 'form_params' => [
- 'device' => $devices[count($devices)]['device'],
- ],
- ]);
+ if ($this->flow === 'cookie') {
+ $this->setCookie('refresh_token', $initial_refresh_token, $domain);
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $initial_refresh_token,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $initial_refresh_token,
+ ];
+ }
+
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token', $request_options);
$this->assertEquals(401, $response->getStatusCode());
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_obsolete_refresh_token', $body['code']);
@@ -325,19 +516,34 @@ public function testTokenRefreshWithInvalidRefreshToken(string $refreshToken): v
],
]);
$body = json_decode($response->getBody()->getContents(), true);
- $this->assertEquals('jwt_auth_no_auth_cookie', $body['code']);
+
+ if ($this->flow === 'cookie') {
+ $this->assertEquals('jwt_auth_no_auth_cookie', $body['code']);
+ } else {
+ $this->assertEquals('jwt_auth_no_refresh_token', $body['code']);
+ }
$this->assertEquals(401, $response->getStatusCode());
$this->assertEquals(false, $body['success']);
- $cookies = [
- 'refresh_token' => $refreshToken,
- ];
- $domain = $this->getDomain();
- $cookies = CookieJar::fromArray($cookies, $domain);
+ $request_options = array();
+ if ($this->flow === 'cookie') {
+ $cookies = [
+ 'refresh_token' => $refreshToken,
+ ];
+ $domain = $this->getDomain();
+ $cookies = CookieJar::fromArray($cookies, $domain);
+ $request_options['cookies'] = $cookies;
+ } elseif ($this->flow === 'body') {
+ $request_options[\GuzzleHttp\RequestOptions::JSON] = [
+ 'refresh_token' => $refreshToken,
+ ];
+ } else {
+ $request_options['form_params'] = [
+ 'refresh_token' => $refreshToken,
+ ];
+ }
- $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', [
- 'cookies' => $cookies,
- ]);
+ $response = $this->client->post('/wp-json/jwt-auth/v1/token/refresh', $request_options);
$body = json_decode($response->getBody()->getContents(), true);
$this->assertEquals('jwt_auth_obsolete_refresh_token', $body['code']);
$this->assertEquals(401, $response->getStatusCode());
diff --git a/tests/src/RestTestTrait.php b/tests/src/RestTestTrait.php
index 4304c65..359f924 100644
--- a/tests/src/RestTestTrait.php
+++ b/tests/src/RestTestTrait.php
@@ -54,6 +54,7 @@ protected function setUp(): void {
$this->client = new Client($this->httpClientConfig);
$this->username = $_ENV['USERNAME'] ?? null;
$this->password = $_ENV['PASSWORD'] ?? null;
+ $this->flow = $_ENV['FLOW'];
}
protected function setCookie($name, $value, $domain): CookieJar {