Skip to content

Commit b7ea5a0

Browse files
committed
Add token validate endpoint
1 parent 191ac93 commit b7ea5a0

File tree

4 files changed

+322
-2
lines changed

4 files changed

+322
-2
lines changed

readme.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,27 @@ That request would be like this:
116116
```bash
117117
curl -X POST https://example.org/wp-json/wp/v2/token \
118118
-F refresh_token=YOUR_REFRESH_TOKEN
119+
```
120+
121+
You can also check if the token is still valid and when it expires:
122+
123+
```bash
124+
curl -X GET https://sample.org/wp-json/wp/v2/token/validate \
125+
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
126+
```
127+
128+
```javascript
129+
{
130+
"code": "rest_authentication_valid_access_token",
131+
"message": "Valid access token.",
132+
"data": {
133+
"status": 200,
134+
"exp": 604800
135+
}
136+
}
119137
```
120138

121-
## Generate Application Passwords ##
139+
## Generate Key-pairs ##
122140

123141
In order to generate a token you first need to create an application password, or what we also refer to as a key-pair.
124142
To create a key-pair you have to first log into the WordPress administrative panel and go to your profile page. There

readme.txt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,25 @@ curl -X POST https://example.org/wp-json/wp/v2/token \
115115
-F refresh_token=YOUR_REFRESH_TOKEN
116116
```
117117

118-
== Generate Application Passwords ==
118+
You can also check if the token is still valid and when it expires:
119+
120+
```bash
121+
curl -X GET https://sample.org/wp-json/wp/v2/token/validate \
122+
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
123+
```
124+
125+
```javascript
126+
{
127+
"code": "rest_authentication_valid_access_token",
128+
"message": "Valid access token.",
129+
"data": {
130+
"status": 200,
131+
"exp": 604800
132+
}
133+
}
134+
```
135+
136+
== Generate Key-pairs ==
119137

120138
In order to generate a token you first need to create an application password, or what we also refer to as a key-pair.
121139
To create a key-pair you have to first log into the WordPress administrative panel and go to your profile page. There

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

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,204 @@ public function test_decode_token() {
583583
$this->assertEquals( $validate_token->get_error_code(), 'rest_authentication_token_error' );
584584
}
585585

586+
/**
587+
* Test validate().
588+
*
589+
* @covers ::validate()
590+
* @since 0.1
591+
*/
592+
public function test_validate() {
593+
$user_data = array(
594+
'role' => 'administrator',
595+
'user_login' => 'testuser',
596+
'user_pass' => 'testpassword',
597+
'user_email' => '[email protected]',
598+
);
599+
600+
$user_id = $this->factory->user->create( $user_data );
601+
602+
$jwt = json_decode(
603+
wp_json_encode(
604+
array(
605+
'iss' => get_bloginfo( 'url' ),
606+
'exp' => time() + WEEK_IN_SECONDS,
607+
'data' => array(
608+
'user' => array(
609+
'id' => $user_id,
610+
'type' => 'wp_user',
611+
'user_login' => 'testuser',
612+
'user_email' => '[email protected]',
613+
),
614+
),
615+
)
616+
)
617+
);
618+
619+
// Invalid HTTP Authorization Header.
620+
$mock = $this->getMockBuilder( get_class( $this->token ) )
621+
->setMethods(
622+
array(
623+
'get_auth_header',
624+
)
625+
)
626+
->getMock();
627+
$mock->method( 'get_auth_header' )->willReturn( new WP_Error() );
628+
629+
$validate = $mock->validate();
630+
$this->assertEquals( 'rest_authentication_invalid_bearer_token', $validate['code'] );
631+
$this->assertEquals( 403, $validate['data']['status'] );
632+
633+
// Invalid Bearer token.
634+
$mock = $this->getMockBuilder( get_class( $this->token ) )
635+
->setMethods(
636+
array(
637+
'get_auth_header',
638+
'get_token',
639+
)
640+
)
641+
->getMock();
642+
$mock->method( 'get_auth_header' )->willReturn( true );
643+
$mock->method( 'get_token' )->willReturn( new WP_Error() );
644+
645+
$validate = $mock->validate();
646+
$this->assertEquals( 'rest_authentication_invalid_bearer_token', $validate['code'] );
647+
$this->assertEquals( 403, $validate['data']['status'] );
648+
649+
// Invalid Bearer token.
650+
$mock = $this->getMockBuilder( get_class( $this->token ) )
651+
->setMethods(
652+
array(
653+
'get_auth_header',
654+
'get_token',
655+
'decode_token',
656+
)
657+
)
658+
->getMock();
659+
$mock->method( 'get_auth_header' )->willReturn( true );
660+
$mock->method( 'get_token' )->willReturn( true );
661+
$mock->method( 'decode_token' )->willReturn( new WP_Error() );
662+
663+
$validate = $mock->validate();
664+
$this->assertEquals( 'rest_authentication_invalid_bearer_token', $validate['code'] );
665+
$this->assertEquals( 403, $validate['data']['status'] );
666+
667+
// Invalid token issuer.
668+
$mock = $this->getMockBuilder( get_class( $this->token ) )
669+
->setMethods(
670+
array(
671+
'get_auth_header',
672+
'get_token',
673+
'decode_token',
674+
'validate_issuer',
675+
)
676+
)
677+
->getMock();
678+
$mock->method( 'get_auth_header' )->willReturn( true );
679+
$mock->method( 'get_token' )->willReturn( true );
680+
$mock->method( 'decode_token' )->willReturn( $jwt );
681+
$mock->method( 'validate_issuer' )->willReturn( new WP_Error() );
682+
683+
$validate = $mock->validate();
684+
$this->assertEquals( 'rest_authentication_invalid_bearer_token', $validate['code'] );
685+
$this->assertEquals( 403, $validate['data']['status'] );
686+
687+
// Invalid token user.
688+
$mock = $this->getMockBuilder( get_class( $this->token ) )
689+
->setMethods(
690+
array(
691+
'get_auth_header',
692+
'get_token',
693+
'decode_token',
694+
'validate_issuer',
695+
'validate_user',
696+
)
697+
)
698+
->getMock();
699+
$mock->method( 'get_auth_header' )->willReturn( true );
700+
$mock->method( 'get_token' )->willReturn( true );
701+
$mock->method( 'decode_token' )->willReturn( $jwt );
702+
$mock->method( 'validate_issuer' )->willReturn( true );
703+
$mock->method( 'validate_user' )->willReturn( new WP_Error() );
704+
705+
$validate = $mock->validate();
706+
$this->assertEquals( 'rest_authentication_invalid_bearer_token', $validate['code'] );
707+
$this->assertEquals( 403, $validate['data']['status'] );
708+
709+
// Token has expired.
710+
$mock = $this->getMockBuilder( get_class( $this->token ) )
711+
->setMethods(
712+
array(
713+
'get_auth_header',
714+
'get_token',
715+
'decode_token',
716+
'validate_issuer',
717+
'validate_user',
718+
'validate_expiration',
719+
)
720+
)
721+
->getMock();
722+
$mock->method( 'get_auth_header' )->willReturn( true );
723+
$mock->method( 'get_token' )->willReturn( true );
724+
$mock->method( 'decode_token' )->willReturn( $jwt );
725+
$mock->method( 'validate_issuer' )->willReturn( true );
726+
$mock->method( 'validate_user' )->willReturn( true );
727+
$mock->method( 'validate_expiration' )->willReturn( new WP_Error() );
728+
729+
$validate = $mock->validate();
730+
$this->assertEquals( 'rest_authentication_expired_bearer_token', $validate['code'] );
731+
$this->assertEquals( 403, $validate['data']['status'] );
732+
733+
// Valid Access Token.
734+
$mock = $this->getMockBuilder( get_class( $this->token ) )
735+
->setMethods(
736+
array(
737+
'get_auth_header',
738+
'get_token',
739+
'decode_token',
740+
'validate_issuer',
741+
'validate_user',
742+
'validate_expiration',
743+
)
744+
)
745+
->getMock();
746+
$mock->method( 'get_auth_header' )->willReturn( true );
747+
$mock->method( 'get_token' )->willReturn( true );
748+
$mock->method( 'decode_token' )->willReturn( $jwt );
749+
$mock->method( 'validate_issuer' )->willReturn( true );
750+
$mock->method( 'validate_user' )->willReturn( true );
751+
$mock->method( 'validate_expiration' )->willReturn( true );
752+
753+
$validate = $mock->validate();
754+
$this->assertEquals( 'rest_authentication_valid_access_token', $validate['code'] );
755+
$this->assertEquals( 200, $validate['data']['status'] );
756+
757+
$jwt->data->user->token_type = 'refresh';
758+
759+
// Valid Refresh Token.
760+
$mock = $this->getMockBuilder( get_class( $this->token ) )
761+
->setMethods(
762+
array(
763+
'get_auth_header',
764+
'get_token',
765+
'decode_token',
766+
'validate_issuer',
767+
'validate_user',
768+
'validate_expiration',
769+
)
770+
)
771+
->getMock();
772+
$mock->method( 'get_auth_header' )->willReturn( true );
773+
$mock->method( 'get_token' )->willReturn( true );
774+
$mock->method( 'decode_token' )->willReturn( $jwt );
775+
$mock->method( 'validate_issuer' )->willReturn( true );
776+
$mock->method( 'validate_user' )->willReturn( true );
777+
$mock->method( 'validate_expiration' )->willReturn( true );
778+
779+
$validate = $mock->validate();
780+
$this->assertEquals( 'rest_authentication_valid_refresh_token', $validate['code'] );
781+
$this->assertEquals( 200, $validate['data']['status'] );
782+
}
783+
586784
/**
587785
* Test validate_token().
588786
*

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ public static function get_rest_uri() {
9090
* @see register_rest_route()
9191
*/
9292
public function register_routes() {
93+
$args = array(
94+
'methods' => WP_REST_Server::READABLE,
95+
'callback' => array( $this, 'validate' ),
96+
);
97+
register_rest_route( self::_NAMESPACE_, '/' . self::_REST_BASE_ . '/validate', $args );
98+
9399
$args = array(
94100
'methods' => WP_REST_Server::CREATABLE,
95101
'callback' => array( $this, 'generate_token' ),
@@ -169,6 +175,16 @@ public function get_item_schema() {
169175
),
170176
),
171177
),
178+
'exp' => array(
179+
'description' => esc_html__( 'The number of seconds until the token expires.', 'jwt-auth' ),
180+
'type' => 'integer',
181+
'readonly' => true,
182+
),
183+
'refresh_token' => array(
184+
'description' => esc_html__( 'Refresh JSON Web Token.', 'jwt-auth' ),
185+
'type' => 'string',
186+
'readonly' => true,
187+
),
172188
),
173189
);
174190

@@ -594,6 +610,76 @@ public function decode_token( $token ) {
594610
}
595611
}
596612

613+
/**
614+
* Determine if a valid Bearer token has been provided and return when it expires.
615+
*
616+
* @return array Return information about whether the token has expired or not.
617+
*/
618+
public function validate() {
619+
620+
$response = array(
621+
'code' => 'rest_authentication_invalid_bearer_token',
622+
'message' => __( 'Invalid bearer token.', 'jwt-auth' ),
623+
'data' => array(
624+
'status' => 403,
625+
),
626+
);
627+
628+
// Get HTTP Authorization Header.
629+
$header = $this->get_auth_header();
630+
if ( is_wp_error( $header ) ) {
631+
return $response;
632+
}
633+
634+
// Get the Bearer token from the header.
635+
$token = $this->get_token( $header );
636+
if ( is_wp_error( $token ) ) {
637+
return $response;
638+
}
639+
640+
// Decode the token.
641+
$jwt = $this->decode_token( $token );
642+
if ( is_wp_error( $jwt ) ) {
643+
return $response;
644+
}
645+
646+
// Determine if the token issuer is valid.
647+
$issuer_valid = $this->validate_issuer( $jwt->iss );
648+
if ( is_wp_error( $issuer_valid ) ) {
649+
return $response;
650+
}
651+
652+
// Determine if the token user is valid.
653+
$user_valid = $this->validate_user( $jwt );
654+
if ( is_wp_error( $user_valid ) ) {
655+
return $response;
656+
}
657+
658+
// Determine if the token has expired.
659+
$expiration_valid = $this->validate_expiration( $jwt );
660+
if ( is_wp_error( $expiration_valid ) ) {
661+
$response['code'] = 'rest_authentication_expired_bearer_token';
662+
$response['message'] = __( 'Expired bearer token.', 'jwt-auth' );
663+
return $response;
664+
}
665+
666+
$response = array(
667+
'code' => 'rest_authentication_valid_access_token',
668+
'message' => __( 'Valid access token.', 'jwt-auth' ),
669+
'data' => array(
670+
'status' => 200,
671+
'exp' => $jwt->exp - time(),
672+
),
673+
);
674+
675+
if ( isset( $jwt->data->user->token_type ) && 'refresh' === $jwt->data->user->token_type ) {
676+
$response['code'] = 'rest_authentication_valid_refresh_token';
677+
$response['message'] = __( 'Valid refresh token.', 'jwt-auth' );
678+
}
679+
680+
return $response;
681+
}
682+
597683
/**
598684
* Determine if a valid Bearer token has been provided.
599685
*

0 commit comments

Comments
 (0)