Skip to content

Implement Application Passwords for API-based authentication.#540

Closed
georgestephanis wants to merge 53 commits intoWordPress:masterfrom
georgestephanis:add/application-passwords
Closed

Implement Application Passwords for API-based authentication.#540
georgestephanis wants to merge 53 commits intoWordPress:masterfrom
georgestephanis:add/application-passwords

Conversation

@georgestephanis
Copy link

@georgestephanis georgestephanis commented Sep 18, 2020

This is an alternative to Basic Auth in #169

Based off existing work in https://github.com/WordPress/application-passwords

PR in progress, collaborating with @TimothyBJacobs for the moment. Trying to make sure this is workable to provide an alternative to Basic Auth.

Trac ticket: https://core.trac.wordpress.org/ticket/42790

Also, worth acknowledging all the folks who have contributed to the Application Passwords plugin thus far, especially @kasparsd who has put in a ton of effort.:

https://github.com/WordPress/application-passwords/graphs/contributors

make/core proposal link: https://make.wordpress.org/core/2020/09/23/proposal-rest-api-authentication-application-passwords/

Not 100% sure it works, still need to build and test, but this is a starting point for future commits to get it shipshape.

There's also a number of core tweaks in here that need to get spread out to better places within the codebase.
If there is no current user authenticated via other means, clear
the cached lack of user, so that an authenticate check can set it
properly.

This is done because for authentications such as Application
Passwords, we don't want it to be accepted unless the current HTTP
 request is an API request, which can't always be identified early
enough in evaluation.
@georgestephanis
Copy link
Author

Added some changes since the last comment phase -- spread the code out across the admin in more relevant places.

Also moves the list table back outside of the .form-table to fix styling conflicts with the list table.
Use wp.apiRequest instead of raw jQuery.ajax.
Use wp.i18n for JS translation instead of translating in PHP.
Remove localization for application-passwords.js.
Also removes some phpcs comments that core doesn't use.
@TimothyBJacobs
Copy link
Member

Instead of suggesting a user to edit their .htaccess, we should probably add it to the default config in WP_Rewrite::mod_rewrite_rules as described in https://core.trac.wordpress.org/ticket/39224. Related https://core.trac.wordpress.org/ticket/47077

@TimothyBJacobs
Copy link
Member

We should also populate rest_authentication_errors with an error message if the app auth fails.

@TimothyBJacobs
Copy link
Member

@TimothyBJacobs
Copy link
Member

Let's also move test-basic-authorization-header to a Site Health test instead.

This should rarely be an issue now since we include the
rewrite in the default htaccess.

Once the Site Health REST API endpoints land, the check
can be moved there. See #48105.
@TimothyBJacobs
Copy link
Member

I think we should require the WordPress site be on SSL. And also verify that both the success and reject URLs are https urls as well.

Ideally, we'd also mandate a state parameter be included. Since the URLs are completely arbitrary, client's could include it themselves, but I think it'd be a good idea to force developers to include it to encourage them to implement CSRF protections on the client side.

@georgestephanis
Copy link
Author

And also verify that both the success and reject URLs are https urls as well.

Possibly to manually upgrade or display a warning for http urls, but please no for mandating https --

It was explicitly written to account for custom protocols such as WordPress:// so that apps could open a browser tab for the user to authenticate, with a return url that would pass the token directly back into the app upon completion.

@georgestephanis
Copy link
Author

The wp-admin/authorize-application.php page would look a lot better if it had a look/aesthetic similar to that of the login page... There really is no need to have it in the dashboard itself.

I do kinda wanna make it obvious that the user can just walk away from this process if they choose to and not have it feel like a "squeeze page" that forces a choice. I also hesitate on taking over the entire admin ui unless it's well discussed a conscious decision.

This introduces a new function wp_is_authorize_application_password_request_valid that contains an action to allow for removing this error if desired.

This also allows for developers to add additional constraints to the application request such as mandating that the application has been approved by an administrator.
This fixes an issue where the details of a newly added password
weren't viewable on mobile.

Props dinhtungdu.
From WordPress/application-passwords#120.
@TimothyBJacobs TimothyBJacobs marked this pull request as ready for review September 26, 2020 21:06
TimothyBJacobs and others added 21 commits September 26, 2020 17:23
Add: confirmation before delete passwords
JS hooks are introduced to customize the data sent to the REST API and perform actions after an app is approved/rejected.

Display hooks are added to the authorize-application.php page to add
additional form fields or customize the output.
Previously it'd only fire when used in wp_validate_application_password().
This meant the hook wouldn't fire during XML-RPC requests.
Core doesn't apply this level of sanitization.
The Passwords list table can now be extended by plugins. This requires the
creation of a "fake" screen id of `application-passwords-user`. Otherwise,
the list table uses the user screen id which isn't unique to the app passwords
instance.

The JS templating is now also moved to the table so plugins can now add their
own templates for their columns. I've also moved the data formatting into the
underscore template so we can pass the raw response to the template.
This allows for plugins to support things like read-only support,
scoped capabilities, expiration, etc...
This can be used for implementing read-only passwords for instance.
The determine_current_user hook doesn't support a WP_Error return type,
instead the input should be passed through.
Rearranges the App Passwords class to follow CRUD.
Use static:: to allow for subclassing.
Switches the user-edit an inline role="alert" notice. The modal behavior we were
using was not accessible and can be a jarring experience.

Implements speak calls after passwords are revoked.

Switches auth-app to also use an inline notice.
*
* @type string
*/
const USERMETA_KEY_APPLICATION_PASSWORDS = '_application_passwords';
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't an application password be lock to a single site on a multisite? Shouldn't this be prefixed this site id?

Copy link
Author

@georgestephanis georgestephanis Oct 8, 2020

Choose a reason for hiding this comment

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

Nope. If someone wants to use the WordPress mobile apps, for example, it would suck for them to log into each individual site in their multisite install individually.

Just as interactive login sessions aren't locked to a single site when in a multisite environment, api authentication is also supported across the install.

@TimothyBJacobs
Copy link
Member

Merged in 1856d0f.

$hashed_passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID );

foreach ( $hashed_passwords as $key => $item ) {
if ( ! wp_check_password( $password, $item['password'], $user->ID ) ) {
Copy link

Choose a reason for hiding this comment

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

There might be a more efficient way to do this, but it'd require you to adopt a split tokens methodology.

Con: Your generated passwords will necessarily be longer as a result.

Pro: You don't need to check multiple password hashes on API authentication.

The way the code is written, it could be a little slow to handle requests for users with a lot of application passwords.


How to make the change:

  1. Make the generated passwords longer. (e.g. 24 -> 32 chars)

  2. The first 8 characters are now used as a separate identifier for this database lookup, and are included in the password hash calculation but are stored alongside it as plaintext. (This is provably as secure as 24 character passwords, but you get to side-step this performance pain.)

$errors->add( 'updated', __( '<strong>You have successfully updated WordPress!</strong> Please log back in to see what&#8217;s new.' ), 'message' );
} elseif ( WP_Recovery_Mode_Link_Service::LOGIN_ACTION_ENTERED === $action ) {
$errors->add( 'enter_recovery_mode', __( 'Recovery Mode Initialized. Please log in to continue.' ), 'message' );
} elseif ( isset( $_GET['redirect_to'] ) && false !== strpos( $_GET['redirect_to'], 'wp-admin/authorize-application.php' ) ) {
Copy link

Choose a reason for hiding this comment

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

Accessing superglobals makes me feel less than fuzzy, but none of the subsequent uses are dangerous, so I think this is fine.

__( 'Sorry, you are not allowed to manage application passwords for this user.' ),
array( 'status' => rest_authorization_required_code() )
);
}
Copy link

Choose a reason for hiding this comment

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

Is this a satisfactory permission check? Does it make sense that edit_user implies manage application passwords, or should that be a separate permission bit?

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