Skip to content

Commit e65055d

Browse files
authored
Merge pull request #81 from wp-graphql/release/v0.4.0
Release/v0.4.0
2 parents d77da3f + 827f263 commit e65055d

36 files changed

+186
-7326
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ vendor/*
1414
c3.php
1515
!/tests
1616
/tests/*.suite.yml
17+
/tests/_output
1718
.env
1819
.codeception.yml
1920
composer.lock

README.md

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ This plugin was initially based off the `wp-api-jwt-auth` plugin by Enrique Chav
1414

1515
## Install, Activate & Setup
1616

17-
You can install and activate the plugin like any WordPress plugin. Download the .zip from Github and add to your plugins directory, then activate.
17+
You can install and activate the plugin like any WordPress plugin. Download the .zip from Github and add to your plugins directory, then activate.
1818

19-
JWT uses a Secret defined on the server to validate the signing of tokens.
19+
JWT uses a Secret defined on the server to validate the signing of tokens.
2020

2121
It's recommended that you use something like the WordPress Salt generator (https://api.wordpress.org/secret-key/1.1/salt/) to generate a Secret.
2222

@@ -25,7 +25,7 @@ You can define a Secret like so:
2525
define( 'GRAPHQL_JWT_AUTH_SECRET_KEY', 'your-secret-token' );
2626
```
2727

28-
Or you can use the filter `graphql_jwt_auth_secret_key` to set a Secret like so:
28+
Or you can use the filter `graphql_jwt_auth_secret_key` to set a Secret like so:
2929

3030
```
3131
add_filter( 'graphql_jwt_auth_secret_key', function() {
@@ -51,15 +51,19 @@ For NGINX, this may work: https://serverfault.com/questions/511206/nginx-forward
5151

5252
## How the plugin Works
5353

54-
This plugin adds a new `login` mutation to the WPGraphQL Schema.
54+
### Login User
5555

56-
This can be used like so:
56+
This plugin adds a new `login` mutation to the WPGraphQL Schema.
5757

58-
```
58+
This can be used like so:
59+
60+
**Input-Type:** `LoginUserInput!`
61+
62+
```graphql
5963
mutation LoginUser {
6064
login( input: {
61-
clientMutationId:"uniqueId"
62-
username: "your_login"
65+
clientMutationId: "uniqueId",
66+
username: "your_login",
6367
password: "your password"
6468
} ) {
6569
authToken
@@ -71,13 +75,52 @@ mutation LoginUser {
7175
}
7276
```
7377

74-
The `authToken` that is received in response to the login mutation can then be stored in local storage (or similar) and
75-
used in subsequent requests as an HTTP Authorization header to Authenticate the user prior to execution of the
76-
GraphQL request.
78+
The `authToken` that is received in response to the login mutation can then be stored in local storage (or similar) and
79+
used in subsequent requests as an HTTP Authorization header to Authenticate the user prior to execution of the
80+
GraphQL request.
7781

7882
- **Set authorization header in Apollo Client**: https://www.apollographql.com/docs/react/networking/authentication/#header
7983
- **Set authorization header in Relay Modern**: https://relay.dev/docs/en/network-layer.html
8084
- **Set authorization header in Axios**: https://github.com/axios/axios#axioscreateconfig
8185

86+
87+
### Register User
88+
89+
**Input-Type:** `RegisterUserInput!`
90+
91+
```graphql
92+
mutation RegisterUser {
93+
registerUser(
94+
input: {
95+
clientMutationId: "uniqueId",
96+
username: "your_username",
97+
password: "your_password",
98+
email: "your_email"
99+
}) {
100+
user {
101+
jwtAuthToken
102+
jwtRefreshToken
103+
}
104+
}
105+
}
106+
```
107+
108+
### Refresh Auth Token
109+
110+
**Input-Type:** `RefreshJwtAuthTokenInput!`
111+
112+
```graphql
113+
mutation RefreshAuthToken {
114+
refreshJwtAuthToken(
115+
input: {
116+
clientMutationId: "uniqueId"
117+
jwtRefreshToken: "your_refresh_token",
118+
}) {
119+
authToken
120+
}
121+
}
122+
```
123+
124+
82125
## Example using GraphiQL
83126
![Example using GraphiQL](https://github.com/wp-graphql/wp-graphql-jwt-authentication/blob/master/img/jwt-auth-example.gif?raw=true)

src/Auth.php

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ public static function login_and_get_token( $username, $password ) {
6868
* The token is signed, now create the object with basic user data to send to the client
6969
*/
7070
$response = [
71-
'authToken' => self::get_signed_token( $user ),
72-
'refreshToken' => self::get_refresh_token( $user ),
71+
'authToken' => self::get_signed_token( wp_get_current_user() ),
72+
'refreshToken' => self::get_refresh_token( wp_get_current_user() ),
7373
'user' => DataSource::resolve_user( $user->data->ID, \WPGraphQL::get_app_context() ),
7474
'id' => $user->data->ID,
7575
];
@@ -123,7 +123,8 @@ public static function get_token_expiration() {
123123
/**
124124
* Retrieves validates user and retrieve signed token
125125
*
126-
* @param User|WP_User $user Owner of the token.
126+
* @param \WP_User $user Owner of the token.
127+
* @param bool $cap_check Whether to check capabilities when getting the token
127128
*
128129
* @return null|string
129130
*/
@@ -200,11 +201,13 @@ protected static function get_signed_token( $user, $cap_check = true ) {
200201
*/
201202
public static function get_user_jwt_secret( $user_id ) {
202203

204+
$is_revoked = Auth::is_jwt_secret_revoked( $user_id );
205+
203206
/**
204207
* If the secret has been revoked, throw an error
205208
*/
206-
if ( true === Auth::is_jwt_secret_revoked( $user_id ) ) {
207-
return new \WP_Error( 'graphql-jwt-revoked-secret', __( 'The JWT Auth secret cannot be returned', 'wp-graphql-jwt-authentication' ) );
209+
if ( true === (bool) $is_revoked ) {
210+
return null;
208211
}
209212

210213
/**
@@ -216,11 +219,11 @@ public static function get_user_jwt_secret( $user_id ) {
216219
$capability = apply_filters( 'graphql_jwt_auth_edit_users_capability', 'edit_users', $user_id );
217220

218221
/**
219-
* If the request is not from the current_user AND the current_user doesn't have the proper capabilities, don't return the secret
222+
* If the request is not from the current_user or the current_user doesn't have the proper capabilities, don't return the secret
220223
*/
221224
$is_current_user = ( $user_id === get_current_user_id() ) ? true : false;
222225
if ( ! $is_current_user && ! current_user_can( $capability ) ) {
223-
return new \WP_Error( 'graphql-jwt-improper-capabilities', __( 'The JWT Auth secret for this user cannot be returned', 'wp-graphql-jwt-authentication' ) );
226+
return null;
224227
}
225228

226229
/**
@@ -232,7 +235,7 @@ public static function get_user_jwt_secret( $user_id ) {
232235
* If there is no stored secret, or it's not a string
233236
*/
234237
if ( empty( $secret ) || ! is_string( $secret ) ) {
235-
Auth::issue_new_user_secret( $user_id );
238+
$secret = Auth::issue_new_user_secret( $user_id );
236239
}
237240

238241
/**
@@ -291,13 +294,21 @@ public static function is_jwt_secret_revoked( $user_id ) {
291294
* Public method for getting an Auth token for a given user
292295
*
293296
* @param \WP_USer $user The user to get the token for
297+
* @param boolean $cap_check Whether to check capabilities. Default is true.
294298
*
295299
* @return null|string
296300
*/
297301
public static function get_token( $user, $cap_check = true ) {
298302
return self::get_signed_token( $user, $cap_check );
299303
}
300304

305+
/**
306+
* Given a WP_User, this returns a refresh token for the user
307+
* @param \WP_User $user A WP_User object
308+
* @param bool $cap_check
309+
*
310+
* @return null|string
311+
*/
301312
public static function get_refresh_token( $user, $cap_check = true ) {
302313

303314
self::$is_refresh_token = true;
@@ -309,6 +320,7 @@ public static function get_refresh_token( $user, $cap_check = true ) {
309320
*/
310321
add_filter( 'graphql_jwt_auth_token_before_sign', function( $token, \WP_User $user ) {
311322
$secret = Auth::get_user_jwt_secret( $user->ID );
323+
312324
if ( ! empty( $secret ) && ! is_wp_error( $secret ) && true === self::is_refresh_token() ) {
313325

314326
/**
@@ -415,7 +427,7 @@ public static function filter_determine_current_user( $user ) {
415427
*
416428
* @return mixed|boolean|\WP_Error
417429
*/
418-
public static function revoke_user_secret( int $user_id ) {
430+
public static function revoke_user_secret( $user_id ) {
419431

420432
/**
421433
* Filter the capability that is tied to editing/viewing user JWT Auth info
@@ -530,7 +542,7 @@ public static function validate_token( $token = null, $refresh = false ) {
530542
* @since 0.0.1
531543
*/
532544
if ( empty( $auth_header ) ) {
533-
return false;
545+
return $token;
534546
} else {
535547
/**
536548
* The HTTP_AUTHORIZATION is present verify the format
@@ -545,52 +557,59 @@ public static function validate_token( $token = null, $refresh = false ) {
545557
* If there's no secret key, throw an error as there needs to be a secret key for Auth to work properly
546558
*/
547559
if ( ! self::get_secret_key() ) {
548-
throw new \Exception( __( 'JWT is not configured properly', 'wp-graphql-jwt-authentication' ) );
560+
self::set_status( 403 );
561+
return new \WP_Error( 'invalid-secret-key', __( 'JWT is not configured properly', 'wp-graphql-jwt-authentication' ) );
549562
}
550563

564+
565+
551566
/**
552-
* Try to decode the token
567+
* Decode the Token
553568
*/
554-
try {
569+
JWT::$leeway = 60;
555570

556-
/**
557-
* Decode the Token
558-
*/
559-
JWT::$leeway = 60;
571+
$secret = self::get_secret_key();
560572

561-
$secret = self::get_secret_key();
573+
try {
562574
$token = ! empty( $token ) ? JWT::decode( $token, $secret, [ 'HS256' ] ) : null;
575+
} catch ( \Exception $exception ) {
576+
$token = new \WP_Error( 'invalid-secret-key', $exception->getMessage() );
577+
}
563578

564-
/**
565-
* The Token is decoded now validate the iss
566-
*/
567-
if ( ! isset( $token->iss ) || get_bloginfo( 'url' ) !== $token->iss ) {
568-
throw new \Exception( __( 'The iss do not match with this server', 'wp-graphql-jwt-authentication' ) );
569-
}
579+
/**
580+
* If there's no token listed, just bail now before validating an empty token.
581+
* This will treat the request as a public request
582+
*/
583+
if ( empty( $token ) ) {
584+
return $token;
585+
}
570586

571-
/**
572-
* So far so good, validate the user id in the token
573-
*/
574-
if ( ! isset( $token->data->user->id ) ) {
575-
throw new \Exception( __( 'User ID not found in the token', 'wp-graphql-jwt-authentication' ) );
576-
}
587+
/**
588+
* The Token is decoded now validate the iss
589+
*/
590+
if ( ! isset( $token->iss ) || get_bloginfo( 'url' ) !== $token->iss ) {
591+
return new \WP_Error( 'invalid-jwt', __( 'The iss do not match with this server', 'wp-graphql-jwt-authentication' ) );
592+
}
577593

578-
/**
579-
* If there is a user_secret in the token (refresh tokens) make sure it matches what
580-
*/
581-
if ( isset( $token->data->user->user_secret ) ) {
594+
/**
595+
* So far so good, validate the user id in the token
596+
*/
597+
if ( ! isset( $token->data->user->id ) ) {
598+
return new \WP_Error( 'invalid-jwt', __( 'User ID not found in the token', 'wp-graphql-jwt-authentication' ) );
599+
}
600+
601+
/**
602+
* If there is a user_secret in the token (refresh tokens) make sure it matches what
603+
*/
604+
if ( isset( $token->data->user->user_secret ) ) {
582605

583-
if ( Auth::is_jwt_secret_revoked( $token->data->user->id ) ) {
584-
throw new \Exception( __( 'The User Secret does not match or has been revoked for this user', 'wp-graphql-jwt-authentication' ) );
585-
}
606+
if ( Auth::is_jwt_secret_revoked( $token->data->user->id ) ) {
607+
return new \WP_Error( 'invalid-jwt', __( 'The User Secret does not match or has been revoked for this user', 'wp-graphql-jwt-authentication' ) );
586608
}
609+
}
587610

588-
/**
589-
* If any exceptions are caught
590-
*/
591-
} catch ( \Exception $error ) {
611+
if ( is_wp_error( $token ) ) {
592612
self::set_status( 403 );
593-
return new \WP_Error( 'invalid_token', __( 'The JWT Token is invalid', 'wp-graphql-jwt-authentication' ) );
594613
}
595614

596615
self::$is_refresh_token = false;

0 commit comments

Comments
 (0)