Skip to content

Commit e632e2d

Browse files
authored
Merge pull request #11 from WP-API/refresh-token
Refresh Tokens
2 parents 4a0d1ea + 7abbe76 commit e632e2d

File tree

6 files changed

+489
-115
lines changed

6 files changed

+489
-115
lines changed

jwt-auth.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
* Plugin URI: https://github.com/WP-API/jwt-auth
1212
* Description: Feature plugin to bring JSON Web Token REST API authentication to Core
1313
* Version: 0.1
14-
* Author: XWP, and contributors
14+
* Author: WP-API
1515
* Author URI: https://github.com/WP-API/jwt-auth/graphs/contributors
1616
* Text Domain: jwt-auth
1717
* License: GPL-2.0+
1818
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
1919
* GitHub Plugin URI: https://github.com/WP-API/jwt-auth
20-
* Requires PHP: 5.3.0
20+
* Requires PHP: 5.6.20
2121
* Requires WP: 4.4.0
2222
*/
2323

tests/wp-includes/rest-api/auth/class-test-wp-rest-key-pair.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ public function test_payload() {
299299
$time = time();
300300
$reserved = array(
301301
'iat' => $time, // Token issued at.
302-
'exp' => $time + ( DAY_IN_SECONDS * 7 ), // Token expiry.
302+
'exp' => $time + WEEK_IN_SECONDS, // Token expiry.
303303
'data' => array(
304304
'user' => array(
305305
'type' => 'wp_user',
@@ -318,7 +318,6 @@ public function test_payload() {
318318
);
319319

320320
$payload = $this->key_pair->payload( $reserved, $user );
321-
$this->assertEquals( $reserved['iat'] + ( DAY_IN_SECONDS * 365 ), $payload['exp'] );
322321
$this->assertEquals( $payload['data']['user']['api_key'], 12345 );
323322
}
324323

tests/wp-includes/rest-api/auth/class-test-wp-rest-token.php

Lines changed: 208 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,145 @@ public function test_authenticate() {
198198
remove_filter( 'rest_authentication_is_rest_request', '__return_true' );
199199
}
200200

201+
/**
202+
* Test authenticate_refresh_token().
203+
*
204+
* @covers ::authenticate_refresh_token()
205+
* @since 0.1
206+
*/
207+
public function test_authenticate_refresh_token() {
208+
$user_data = array(
209+
'role' => 'administrator',
210+
'user_login' => 'testuser',
211+
'user_pass' => 'testpassword',
212+
'user_email' => '[email protected]',
213+
);
214+
215+
$request = new WP_REST_Request( 'POST', 'wp/v2/key-pair' );
216+
$user_id = $this->factory->user->create( $user_data );
217+
218+
$jwt = json_decode(
219+
wp_json_encode(
220+
array(
221+
'data' => array(
222+
'user' => array(
223+
'type' => 'wp_user',
224+
'user_login' => 'testuser',
225+
'user_email' => '[email protected]',
226+
),
227+
),
228+
)
229+
)
230+
);
231+
232+
// Another authentication method was used.
233+
$this->assertEquals( 'alt_auth', $this->token->authenticate_refresh_token( 'alt_auth', $request ) );
234+
235+
// Missing `refresh_token` param.
236+
$this->assertFalse( $this->token->authenticate_refresh_token( false, $request ) );
237+
238+
$request->set_param( 'refresh_token', '54321' );
239+
240+
// Decode token error.
241+
$this->assertTrue( is_wp_error( $this->token->authenticate_refresh_token( false, $request ) ) );
242+
243+
$mock = $this->getMockBuilder( get_class( $this->token ) )
244+
->setMethods(
245+
array(
246+
'decode_token',
247+
)
248+
)
249+
->getMock();
250+
$mock->method( 'decode_token' )->willReturn( $jwt );
251+
252+
$response = $mock->authenticate_refresh_token( false, $request );
253+
$this->assertTrue( is_wp_error( $response ) );
254+
$this->assertEquals( $response->get_error_code(), 'rest_authentication_missing_refresh_token_api_key' );
255+
256+
$jwt->data->user->api_key = '12345';
257+
258+
$mock = $this->getMockBuilder( get_class( $this->token ) )
259+
->setMethods(
260+
array(
261+
'decode_token',
262+
)
263+
)
264+
->getMock();
265+
$mock->method( 'decode_token' )->willReturn( $jwt );
266+
267+
$response = $mock->authenticate_refresh_token( false, $request );
268+
$this->assertTrue( is_wp_error( $response ) );
269+
$this->assertEquals( $response->get_error_code(), 'rest_authentication_missing_refresh_token_user_id' );
270+
271+
$jwt->data->user->id = 1234;
272+
273+
$mock = $this->getMockBuilder( get_class( $this->token ) )
274+
->setMethods(
275+
array(
276+
'decode_token',
277+
)
278+
)
279+
->getMock();
280+
$mock->method( 'decode_token' )->willReturn( $jwt );
281+
282+
$response = $mock->authenticate_refresh_token( false, $request );
283+
$this->assertTrue( is_wp_error( $response ) );
284+
$this->assertEquals( $response->get_error_code(), 'rest_authentication_invalid_token_type' );
285+
286+
$jwt->data->user->token_type = 'refresh';
287+
288+
$mock = $this->getMockBuilder( get_class( $this->token ) )
289+
->setMethods(
290+
array(
291+
'decode_token',
292+
)
293+
)
294+
->getMock();
295+
$mock->method( 'decode_token' )->willReturn( $jwt );
296+
297+
$response = $mock->authenticate_refresh_token( false, $request );
298+
$this->assertTrue( is_wp_error( $response ) );
299+
$this->assertEquals( $response->get_error_code(), 'rest_authentication_invalid_refresh_token' );
300+
301+
$jwt->data->user->id = $user_id;
302+
303+
$mock = $this->getMockBuilder( get_class( $this->token ) )
304+
->setMethods(
305+
array(
306+
'decode_token',
307+
)
308+
)
309+
->getMock();
310+
$mock->method( 'decode_token' )->willReturn( $jwt );
311+
312+
$response = $mock->authenticate_refresh_token( false, $request );
313+
$this->assertTrue( is_wp_error( $response ) );
314+
$this->assertEquals( $response->get_error_code(), 'rest_authentication_revoked_api_key' );
315+
316+
$keypairs = array(
317+
array(
318+
'api_key' => '12345',
319+
'api_secret' => wp_hash( '54321' ),
320+
),
321+
);
322+
foreach ( $keypairs as $keypair ) {
323+
add_user_meta( $user_id, $keypair['api_key'], $keypair['api_secret'], true );
324+
}
325+
update_user_meta( $user_id, WP_REST_Key_Pair::_USERMETA_KEY_, $keypairs );
326+
327+
$mock = $this->getMockBuilder( get_class( $this->token ) )
328+
->setMethods(
329+
array(
330+
'decode_token',
331+
)
332+
)
333+
->getMock();
334+
$mock->method( 'decode_token' )->willReturn( $jwt );
335+
336+
$response = $mock->authenticate_refresh_token( false, $request );
337+
$this->assertEquals( '12345', $response->data->api_key );
338+
}
339+
201340
/**
202341
* Test require_token().
203342
*
@@ -251,9 +390,10 @@ public function test_require_token() {
251390
}
252391

253392
/**
254-
* Test generate_token() `rest_authentication_user` filter.
393+
* Test generate_payload() `rest_authentication_user` filter.
255394
*
256395
* @covers ::generate_token()
396+
* @covers ::generate_payload()
257397
* @since 0.1
258398
*/
259399
public function test_generate_token_rest_authentication_user() {
@@ -320,6 +460,7 @@ public function test_generate_token_rest_authentication_user() {
320460
* Test generate_token().
321461
*
322462
* @covers ::generate_token()
463+
* @covers ::generate_payload()
323464
* @since 0.1
324465
*/
325466
public function test_generate_token() {
@@ -354,12 +495,6 @@ public function test_generate_token() {
354495
};
355496
add_filter( 'rest_authentication_token_private_claims', $private_claims );
356497

357-
$token_response = function( $response ) {
358-
$response['refresh_token'] = 54321;
359-
return $response;
360-
};
361-
add_filter( 'rest_authentication_token_response', $token_response );
362-
363498
// Test with correct credentials.
364499
$request->set_param( 'password', $user_data['user_pass'] );
365500
$token = $this->token->generate_token( $request );
@@ -373,10 +508,73 @@ public function test_generate_token() {
373508
$this->assertEquals( $user_data['user_login'], $token['data']['user']['user_login'] );
374509
$this->assertEquals( $user_data['user_email'], $token['data']['user']['user_email'] );
375510
$this->assertEquals( 12345, $token['data']['user']['api_key'] );
376-
$this->assertEquals( 54321, $token['refresh_token'] );
377511

378512
remove_filter( 'rest_authentication_token_private_claims', $private_claims );
379-
remove_filter( 'rest_authentication_token_response', $token_response );
513+
}
514+
515+
/**
516+
* Test append_refresh_token().
517+
*
518+
* @covers ::append_refresh_token()
519+
* @since 0.1
520+
*/
521+
public function test_append_refresh_token() {
522+
$user_data = array(
523+
'role' => 'administrator',
524+
'user_login' => 'testuser',
525+
'user_pass' => 'testpassword',
526+
'user_email' => '[email protected]',
527+
);
528+
529+
$user_id = $this->factory->user->create( $user_data );
530+
$request = new WP_REST_Request( 'POST', 'wp/v2/token' );
531+
532+
// Missing Bearer token from the header.
533+
$mock = $this->getMockBuilder( get_class( $this->token ) )
534+
->setMethods(
535+
array(
536+
'generate_payload',
537+
)
538+
)
539+
->getMock();
540+
$mock->method( 'generate_payload' )->willReturn(
541+
new WP_Error(
542+
'rest_authentication_error',
543+
__( 'An error coming from the payload.', 'jwt-auth' ),
544+
array(
545+
'status' => 403,
546+
)
547+
)
548+
);
549+
550+
$response = $this->token->append_refresh_token( array(), get_user_by( 'id', $user_id ), $request );
551+
$this->assertArrayHasKey( 'refresh_token', $response );
552+
553+
$append_refresh_token = $mock->append_refresh_token( array(), get_user_by( 'id', $user_id ), $request );
554+
$this->assertTrue( is_wp_error( $append_refresh_token ) );
555+
$this->assertEquals( $append_refresh_token->get_error_code(), 'rest_authentication_error' );
556+
}
557+
558+
/**
559+
* Test decode_token().
560+
*
561+
* @covers ::decode_token()
562+
* @since 0.1
563+
*/
564+
public function test_decode_token() {
565+
// Unknown JWT Exception.
566+
$mock = $this->getMockBuilder( get_class( $this->token ) )
567+
->setMethods(
568+
array(
569+
'jwt',
570+
)
571+
)
572+
->getMock();
573+
$mock->method( 'jwt' )->will( $this->throwException( new Exception() ) );
574+
575+
$validate_token = $mock->decode_token( 'bad-token' );
576+
$this->assertTrue( is_wp_error( $validate_token ) );
577+
$this->assertEquals( $validate_token->get_error_code(), 'rest_authentication_token_error' );
380578
}
381579

382580
/**
@@ -529,7 +727,7 @@ public function test_validate_token() {
529727
)
530728
)
531729
->getMock();
532-
$mock->method( 'jwt' )->willReturn( new Exception() );
730+
$mock->method( 'jwt' )->will( $this->throwException( new Exception() ) );
533731

534732
$validate_token = $mock->validate_token();
535733
$this->assertTrue( is_wp_error( $validate_token ) );

wp-admin/js/key-pair.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@
117117
$keyPairSection.after( tmplNewTokenKeyPair( {
118118
name: name,
119119
api_key: apiKey,
120-
access_token: response.access_token
120+
access_token: response.access_token,
121+
refresh_token: response.refresh_token
121122
} ) );
122123

123124
$( document ).on( 'click', '.key-pair-token-download', function( event ) {

wp-includes/rest-api/auth/class-wp-rest-key-pair.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -396,11 +396,6 @@ public function authenticate( $user, WP_REST_Request $request ) {
396396
*/
397397
public function payload( $payload, $user ) {
398398

399-
// Now that token can be revoked, expire the token in 365 days instead of the default 7 days.
400-
if ( isset( $payload['iat'] ) ) {
401-
$payload['exp'] = $payload['iat'] + ( DAY_IN_SECONDS * 365 );
402-
}
403-
404399
// Set the api_key. which we use later to validate a key-pair has not already been revoked.
405400
if ( isset( $user->data->api_key ) && isset( $payload['data']['user'] ) ) {
406401
$payload['data']['user']['api_key'] = $user->data->api_key;
@@ -712,7 +707,7 @@ public function template_new_token_key_pair() {
712707
<# if ( data.message ) { #>
713708
<div class="notice notice-error"><p>{{{ data.message }}}</p></div>
714709
<# } #>
715-
<# if ( ! data.access_token ) { #>
710+
<# if ( ! data.access_token || ! data.refresh_token ) { #>
716711
<p>
717712
<?php
718713
printf(
@@ -738,11 +733,18 @@ public function template_new_token_key_pair() {
738733
<?php
739734
printf(
740735
/* translators: %s: JSON Web Token */
741-
esc_html_x( 'Your new JSON Web Token is: %s', 'JSON Web Token', 'jwt-auth' ),
736+
esc_html_x( 'Your new access token is: %s', 'Access Token', 'jwt-auth' ),
742737
'<kbd>{{ data.access_token }}</kbd>'
743738
);
744739
?>
745-
<p><?php esc_attr_e( 'Be sure to save this JSON Web Token in a safe location, you will not be able to retrieve it ever again. Once you click dismiss it is gone forever.', 'jwt-auth' ); ?></p>
740+
<?php
741+
printf(
742+
/* translators: %s: JSON Web Token */
743+
esc_html_x( 'Your new refresh token is: %s', 'Refresh Token', 'jwt-auth' ),
744+
'<kbd>{{ data.refresh_token }}</kbd>'
745+
);
746+
?>
747+
<p><?php esc_attr_e( 'Be sure to save these JSON Web Tokens in a safe location, you will not be able to retrieve them ever again. Once you click dismiss they\'re is gone forever.', 'jwt-auth' ); ?></p>
746748
</div>
747749
<button class="button button-secondary key-pair-token-download"><?php esc_attr_e( 'Download', 'jwt-auth' ); ?></button>
748750
<# } #>

0 commit comments

Comments
 (0)