Skip to content

[WIP] Version 0.1#1

Merged
kadamwhite merged 57 commits intomasterfrom
develop
Aug 15, 2019
Merged

[WIP] Version 0.1#1
kadamwhite merged 57 commits intomasterfrom
develop

Conversation

@valendesigns
Copy link
Copy Markdown
Collaborator

@valendesigns valendesigns commented Nov 18, 2018

  • REST API Support
  • Add test coverage
  • Setup Travis CI
  • Setup Coveralls
  • Create API user flows & validate
  • Add README.md
  • Add contributors list

@valendesigns valendesigns self-assigned this Nov 18, 2018
Copy link
Copy Markdown
Contributor

@kadamwhite kadamwhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plugin's a WIP prototype, so I have no formal objection to merging as-is if it'll keep us moving; there's lots to be done and this does provide a decent foundation.

However, there's a couple wishlist things I'd like to see here before pushing the green button:

  • A little more rigor around documentation, commenting, and style; on the theory that if we start strong, it'll be easier to end strong
  • A comment thoroughly walking through the flow of requests necessary to generate a token
  • Some skeleton documentation around the overall structure; as evidenced by the conversation in Slack today several of us are not confidant we can suss out the flows from the code alone, so a talk-through would be very valuable.

{
"env": {
"browser": true
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll probably want to clean this up in a similar way to WordPress/grunt-patch-wordpress#72 -- consume the core eslint plugin, etc -- but that's a follow-up, not necessary to resolve for this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulled this out into #3 so we can table it for now and revisit once the important stuff is done 👌

.travis.yml Outdated

# Newer versions like trusty don't have PHP 5.2 or 5.3
# https://blog.travis-ci.com/2017-07-11-trusty-as-default-linux-is-coming
dist: precise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the work we're doing to bump minimum version, let's code towards a 7.x future and only test down to 5.6 at minimum.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can test down to 5.6, which is fine, but we still would support 5.3 through static analysis.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I slightly disagree, 5.3 is long past EOL and the timeline we have for this would put us into a zone where we shouldn't have to worry about it with regard to core development. I don't think it should hold this up but I don't think we need to go all the way back to precise.

jwt-auth.php Outdated

if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
deactivate_plugins( plugin_basename( __FILE__ ) );
wp_die( esc_html__( 'The JWT Auth plugin requires PHP Version 5.3 or above.', 'jwt-auth' ) );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above note about 5.6

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kadamwhite Changing this to 5.6 is not needed. The minimum PHP version required for the code to work is 5.3 and Core is adopting 5.3, so what would the benefit of making this 5.6 be?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per this post: https://wordpress.org/news/2019/04/minimum-php-version-update/ the PHP minimum version for core is, "5.6 now and soon 7+"

$config = getenv( 'WP_TESTS_CONFIG' );

/**
* Supports loading the wp-tests-config.php from a non VVV custom directory.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: let's remove the VVV-specific language.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point the only environment the unit tests work in is VVV, or have been tested in would be more accurate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@valendesigns Do you mean it can only run inside VVV? Because I was able to run it via Laravel Valet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screen Shot 2019-03-19 at 09 17 13

*
* This class only allows REST authentication to the `{api_prefix}/wp/v2/token`
* endpoint with an API key:secret. This allows a user to be identified via the
* REST API without using their login credentials to generate a JSON Web Token.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what's intended here, but as discussed in Slack I think this flow is confusing and that we'll need a request flow example (Either in js, php, whichever, saying, POST to /wp-json/tokens/generate, then take X from that response, etc…) before we make any progress on that.

*/
public function register_routes() {
$args = array(
array(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not clear why this is an array of arrays. Shouldn't these all just be one level? None of the core endpoints use nested arrays in this way, they pass $args = array( 'args' => array(), 'methods' => ... ) etc.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second that!

'callback' => array(
$this,
'delete_key_pair',
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolute style nit but we try to keep these callback arrays to one line in the core controller code, to make it more obvious it's a class method lookup.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second that!

if ( isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { // phpcs:ignore
$keypairs = $this->get_user_key_pairs( $user_id );
if ( ! empty( $keypairs ) ) {
$this->set_user_key_pairs( $user_id, array() );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use a clarifying comment, here or in the function docbloc, clearly explaining what's being cleared.

@@ -0,0 +1,379 @@
<?php

namespace Firebase\JWT;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anybody know the policy on external dependencies like this, in terms of where they should go in the codebase? Most of them go into /wp-includes/[ project ], looking at SimplePie, Requests, etc. I'd expect this should be followed here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved it 👍

@jasonbahl
Copy link
Copy Markdown
Collaborator

I see in the checklist: Add support for WPGraphQL

I think right now, as long as there are appropriate filters/hooks/public methods for any API to hook into, then at the moment it should be WPGraphQL's responsibility to provide some "adapter" to work with this.

There's no official plan (that I know of) to move WPGraphQL into core, so a plugin like this that is targeting core should probably not be concerned with explicit WPGraphQL support, but rather general access for any API layer to make use of.

REST and GraphQL aren't the only API's that will exist for WordPress. Whatever cool new API tech 5 years from now should also be able to make use of whatever foundation is there.


I'll try and review the code shortly and provide thoughts on how I would want to hook in and if any changes are necessary to be able to accomplish what I would need to do

@valendesigns
Copy link
Copy Markdown
Collaborator Author

@kadamwhite I'll work on updates from the review on Monday. Thanks for reviewing!

@jasonbahl Yes the intent is to ensure the current hooks/filters and workflow support WPGraphQL not to write specific code only used for it.

Copy link
Copy Markdown

@renatonascalves renatonascalves left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on my initial review!

$config = getenv( 'WP_TESTS_CONFIG' );

/**
* Supports loading the wp-tests-config.php from a non VVV custom directory.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@valendesigns Do you mean it can only run inside VVV? Because I was able to run it via Laravel Valet.

$config = getenv( 'WP_TESTS_CONFIG' );

/**
* Supports loading the wp-tests-config.php from a non VVV custom directory.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screen Shot 2019-03-19 at 09 17 13

*/
public function register_routes() {
$args = array(
array(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second that!

$args = array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: leave in one line: 'callback' => array( $this, 'generate_key_pair' ) This and others below.

'callback' => array(
$this,
'delete_key_pair',
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second that!

public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => esc_html__( 'Key-pair', 'jwt-auth' ),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be lower case and with the underscore? key_pair?

* @param mixed $user The user that's being authenticated.
* @param WP_REST_Request $request The REST request object.
*
* @return bool|object|mixed
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Add the objects returned: WP_User and WP_Error so that I don't have to read the code/method to know which objects are returned.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically if someone has filtered rest_authentication_user it could be any kind of object. If $user is not equal to false the method returns the value so yes the intention is that it will return a WP_User but that's not a guarantee and will work perfectly with any type of object with the correct properties. If someone wanted to build their own decoupled user system that's possible by filtering rest_authentication_user. I'll update the docblock to call this out.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

K! Thank you! Makes sense!

*
* @param object $jwt The JSON Web Token.
*
* @return object|WP_Error
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here! Which object is that?

@kadamwhite
Copy link
Copy Markdown
Contributor

The key thing we're waiting on here, if you have bandwidth @valendesigns , is this:

A comment thoroughly walking through the flow of requests necessary to generate a token

If we can get a human-language walkthrough of the full auth process, that will help move everything else along.

@ocean90
Copy link
Copy Markdown
Contributor

ocean90 commented Apr 11, 2019

I'm wondering what the reason was for invalidating a token when an email changes in 7ccfc84?
This now means when a user changes their email via the API you'd have to request them to re-login after the change to get a valid token again. This sounds like a bad UX and would be different from the existing cookie validation since only a user_login change invalidates the cookie.

@koke
Copy link
Copy Markdown

koke commented Apr 12, 2019

I’d extend the same question to user_login. I don’t see why a username change would invalidate a token.

I even have doubts about whether a password change should, not sure if there has been a discussion about that, or just going with what cookie authentication does.

@ocean90
Copy link
Copy Markdown
Contributor

ocean90 commented Apr 12, 2019

A password change doesn't invalidate the token currently, which I think is fine.

Since one of the principles of JWT is stateless I'm suggesting to just drop validate_user() altogether. A valid signed and not expired token needs to be trusted. If it's valid let wp_set_current_user( $user_id ); do the rest.

If we follow this we could remove all the user data/type handling to keep it simple. Maybe even use the sub claim for the user ID?

@jasonbahl
Copy link
Copy Markdown
Collaborator

I need to get caught up on the codebase and conversations, but I would submit that Username and password changes should for sure invalidate tokens.

If we go the "stateless" approach, we need to issue very short lived tokens (few minute expiration max) and an additional revokeable longer-lived refresh token.

A token that's valid when it's issued should only be trusted for a very short time.

If a users account is compromised - let's say a malicious co-worker or relative used their username and password - the compromised user should be able to take action, like resetting their password, and the compromised tokens should no longer be valid.

Even think of Netflix, etc which give you a "Sign out of all accounts" option. That's invalidating all previously issued tokens.

The way we've approached this on WPGraphQL JWT Authentication is issuing 2 tokens. A short-lived access/auth token (5 min expiration) and a long-lived (1-year) refresh token.

The authToken can be used to do things like read private data, execute mutations (writes), etc.

The refreshToken is only good for getting a new authToken. If an authToken is compromised, it's limited to 5 minutes, which may even be considered long, especially if you're the one who's token was compromised.

Anyway, our flow looks like this:

  • User logs in and asks for a jwtAuthToken and jwtRefreshToken in repsonse.
  • for future requests, the jwtAuthToken is checked to see if it's expired or not.
    • If not expired, it's added to the headers of the request and used.
    • If it is expired, a pre-flight request is sent using the jwtRefreshToken to ask for a new jwtAuthToken
      • If the request succeeds, a new jwtAuthToken is provided
      • if the request fails (the jwtRefreshToken is invalid. . .manually revoked by an admin, pw reset, etc) then a new jwtAuthToken is not issued and the user must login again to get new tokens.

If we're using a single token (haven't really dug into the code yet) then it absolutely needs to be stateful.

@jeffpaul jeffpaul mentioned this pull request Jul 5, 2019
@kadamwhite
Copy link
Copy Markdown
Contributor

After discussion with @TimothyBJacobs , the size of this PR is prohibitive for a thorough review. We've all had a lot of eyes on it over the past few months; let's land this and iterate rather than letting this linger further. Thank you @valendesigns !

@kadamwhite kadamwhite merged commit a68125c into master Aug 15, 2019
@renatonascalves
Copy link
Copy Markdown

@kadamwhite agree! Cool! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants