[wp-trac] [WordPress Trac] #51939: Basic Auth staging protections conflicts with App Passwords
WordPress Trac
noreply at wordpress.org
Fri Dec 4 15:50:19 UTC 2020
#51939: Basic Auth staging protections conflicts with App Passwords
-----------------------------------+--------------------
Reporter: TimothyBlynJacobs | Owner: (none)
Type: defect (bug) | Status: new
Priority: highest omg bbq | Milestone: 5.6
Component: Application Passwords | Version: 5.6
Severity: blocker | Keywords:
Focuses: rest-api |
-----------------------------------+--------------------
Application Passwords uses Basic Authentication to pass the username and
password authentication data.
The first time `is_user_logged_in` is called during
`WP_REST_Server::serve_request()`, we attempt authentication. If the
`PHP_AUTH_USER` variable isn't populated, we don't attempt App Passwords
authentication at all. If it is present, we attempt to authentication
based on it. If an error is encountered, this is stored and then returned
via the filter in `WP_REST_Server::check_authentication` and the
`rest_application_password_check_errors` callback.
This becomes an issue if the site is globally behind Basic Authentication
of its own. Because now the REST API will see the Basic Auth credentials,
and try to authenticate the user even if it isn't intended. For reference:
https://wordpress.org/support/topic/sitewide-basic-authentication-and-
application-password-causing-issues/#post-13745874
There are a couple of different possible solutions.
There are two goals I'd like to maintain. For it to be a standards based
as possible, and maintain the friendliest DUX. A developer should be able
to tell that there was an issue with authentication.
1. Switch from using the `Authorization` header to something like `WP-
Authorization`. It would follow the same format, but since it is a
different header, there would be no chance of conflict. It would also
allow for using App Passwords when a site is behind Basic Auth. This has a
number of issues though since middleware wouldn't know that this is an
authenticated request anymore. Particularly since we also aren't passing
any cookies. It also breaks DUX of clients that have support for basic
auth built in.
2. Instead of a whole different header, we can use a different
authentication scheme. Such as `WP-App-Password {token}` instead of `Basic
{token}`. This alleviates the issues due to the `Authorization` header not
being used, but we still have the DUX issues.
3. Don't report errors if the password isn't exactly 24 characters, ie the
format of App Passwords. Since App Passwords are unlikely to be typed by
hand, and even if they were, an Application could reject them if they
weren't 24 characters, this would be a fairly good indicator that App
Passwords were attempted to be used. But if the server level Basic Auth
password was 24 characters long, you'd also have this issue.
4. Require the client to prefix the App Password with something like
`wpapp`. This is a more surefire signifier than the password being 24
characters, but it would require clients to juggle adding the prefix or
not for existing App Passwords. Versus some of the following options, this
has the advantage of the signifier being colocated with the other
authentication data.
The following solutions would all **''also''** return authentication
errors in a separate response header. For instance, we could send `X-WP-
Auth-Failed: {code}={message}` or something.
5. That's it. Never return an authentication error in the main body of the
response.
6. Require a signifier in the request to return an authentication error,
if it is missing don't return the error from
`rest_application_password_check_errors().` For instance, a client could
always pass a `X-WP-App-Passwords: true` header. This is quite simple for
a developer to include.
7. If the REST API response is a `401 Unauthorized`, see
`rest_authorization_required_code`, return the authentication error if
any. The `401` status code indicates that we tried to authenticate you as
a user, but we couldn't determine who you were. Versus a `403` indicating
you didn't have sufficient capabilities.
This would be implemented something like this.
{{{#!php
<?php
$authenticated = $this->check_authentication();
if ( ! is_wp_error( $authenticated ) ||
rest_application_password_collect_status( null ) === $authenticated ) {
$result = $this->dispatch( $request );
} else {
$result = $authenticated;
}
$response = $this->dispatch();
// This indicates an error due to the user being logged out instead of
// not having the right caps. See rest_authorization_required_code().
if ( $response->get_status() === 401 && is_wp_error( $authenticated ) ) {
$response = $authenticated;
}
}}}
----
There are some disadvantages to not returning the authentication error
immediately. Let's say you are submitting a comment. If anonymous comments
are on, the comments controller doesn't require you to be logged in. So
you may end up submitting a comment anonymously even if you were trying to
submit it as a logged in user. A similar thing could happen for the route
that was originally reported, a contact form submission.
Of note, we somewhat already have the anonymous issue with cookie auth. If
the nonce is missing, no error is returned. While this is very confusing
to developers who are learning the REST API for the first time, once the
coding error is fixed an error is always returned. Whereas with App
Passwords the error is more of a runtime issue than a coding logic issue,
the App Password might be invalid or deleted.
If at all possible, I'd like to be able to return the authentication error
in the body of the response. It is clearer to clients that an
authentication error happened versus putting that information in the
headers of the response. It also is a higher fidelity transfer mechanism
than the response headers. We report the `$data` included in the
`WP_Error` object returned.
My current preference I think is option 7. This lets us still be standards
based and the vast majority of the time would allow for us to return the
authentication error in the body of the response. That wouldn't catch the
issue with routes that can be anonymous, if we want to also solve that
issue I would prefer option 6.
--
Ticket URL: <https://core.trac.wordpress.org/ticket/51939>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list