Skip to content

Commit

Permalink
Fix login process error reporting
Browse files Browse the repository at this point in the history
WP_Auth0_LoginManager was not processing errors well, was poorly
documented, and might have been improperly exposing error messages.
Incoming URL param errors from Auth0 and configuration issues are caught
earlier in the login process. Error message are not exposed to the
user; instead they are logged for an admin. Thrown errors are
standarized and listed in docblocks.

Fixes #305
  • Loading branch information
joshcanhelp committed Mar 12, 2018
1 parent 02c6bba commit 46e20ec
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 123 deletions.
15 changes: 15 additions & 0 deletions WP_Auth0.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ public function init() {
WP_Auth0_Email_Verification::init();
}

/**
* Is the Auth0 plugin ready to proc
*
* @return bool
*/
public static function ready() {
$options = WP_Auth0_Options::Instance();
if ( ! $options->get( 'domain' ) || ! $options->get( 'client_id' ) || ! $options->get( 'client_id' ) ) {
return FALSE;
}
return TRUE;
}

/**
* Checks it it should update the database connection no enable or disable signups and create or delete
* the rule that will disable social signups.
Expand Down Expand Up @@ -240,9 +253,11 @@ public static function get_plugin_dir_url() {
}

public function a0_register_query_vars( $qvars ) {
$qvars[] = 'error';
$qvars[] = 'error_description';
$qvars[] = 'a0_action';
$qvars[] = 'auth0';
$qvars[] = 'state';
$qvars[] = 'code';
$qvars[] = 'state';
return $qvars;
Expand Down
236 changes: 113 additions & 123 deletions lib/WP_Auth0_LoginManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,19 @@ public function login_auto() {
}

public function init_auth0() {
global $wp_query;

if ( $this->query_vars( 'auth0' ) === null ) {
// Not an Auth0 login process or settings are not configured to allow logins
if ( ! $this->query_vars( 'auth0' ) || ! WP_Auth0::ready() ) {
return;
}

// Catch any incoming errors and stop the login process
// See https://auth0.com/docs/libraries/error-messages
if ( $this->query_vars( 'error_description' ) ) {
$error_msg = sanitize_text_field( $this->query_vars( 'error_description' ) );
$this->die_on_login( $error_msg ? $error_msg : sanitize_text_field( $this->query_vars( 'error' ) ) );
}

// Check for valid state nonce
// See https://auth0.com/docs/protocols/oauth2/oauth-state
$state_decoded = $this->get_state();
Expand All @@ -163,136 +170,124 @@ public function init_auth0() {
} else {
$this->redirect_login();
}
} catch (WP_Auth0_LoginFlowValidationException $e) {
} catch ( WP_Auth0_LoginFlowValidationException $e ) {

// Errors during the OAuth login flow
$this->die_on_login( $e->getMessage(), $e->getCode() );

} catch (WP_Auth0_BeforeLoginException $e) {
} catch ( WP_Auth0_BeforeLoginException $e ) {

// Errors during the WordPress login flow
$this->die_on_login( $e->getMessage(), $e->getCode(), FALSE );

} catch (Exception $e) {

}

}

/**
* Main login flow, Authorization Code Grant
*
* @throws WP_Auth0_BeforeLoginException
* @throws WP_Auth0_LoginFlowValidationException
*
* @see https://auth0.com/docs/api-auth/tutorials/authorization-code-grant
*/
public function redirect_login() {
global $wp_query;

if ( $this->query_vars( 'auth0' ) === null ) {
return;
}

if ( $this->query_vars( 'error_description' ) !== null && $this->query_vars( 'error_description' ) !== '' ) {
throw new WP_Auth0_LoginFlowValidationException( sanitize_text_field( $this->query_vars( 'error_description' ) ) );
}

if ( $this->query_vars( 'error' ) !== null && trim( $this->query_vars( 'error' ) ) !== '' ) {
throw new WP_Auth0_LoginFlowValidationException( sanitize_text_field( $this->query_vars( 'error' ) ) );
}

$code = $this->query_vars( 'code' );
$state_decoded = $this->get_state();

$domain = $this->a0_options->get( 'domain' );

$client_id = $this->a0_options->get( 'client_id' );
$client_secret = $this->a0_options->get( 'client_secret' );

if ( empty( $client_id ) ) {
throw new WP_Auth0_LoginFlowValidationException( __( 'Error: Your Auth0 Client ID has not been entered in the Auth0 SSO plugin settings.', 'wp-auth0' ) );
}
if ( empty( $client_secret ) ) {
throw new WP_Auth0_LoginFlowValidationException( __( 'Error: Your Auth0 Client Secret has not been entered in the Auth0 SSO plugin settings.', 'wp-auth0' ) );
}
if ( empty( $domain ) ) {
throw new WP_Auth0_LoginFlowValidationException( __( 'Error: No Domain defined in Wordpress Administration!', 'wp-auth0' ) );
}

$response = WP_Auth0_Api_Client::get_token( $domain, $client_id, $client_secret, 'authorization_code', array(
// Exchange authorization code for token
$exchange_resp = WP_Auth0_Api_Client::get_token( $domain, $client_id, $client_secret, 'authorization_code', array(
'redirect_uri' => home_url(),
'code' => $code,
'code' => $this->query_vars( 'code' ),
) );

if ( $response instanceof WP_Error ) {
WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => WP_Auth0_Api_Client::get_token()', $response );

error_log( $response->get_error_message() );
$exchange_resp_code = (int) wp_remote_retrieve_response_code( $exchange_resp );
$exchange_resp_body = wp_remote_retrieve_body( $exchange_resp );

throw new WP_Auth0_LoginFlowValidationException( $response->get_error_message() );
}

$data = json_decode( $response['body'] );
if ( 401 === $exchange_resp_code ) {

if ( isset( $data->access_token ) || isset( $data->id_token ) ) {
// Not authorized
WP_Auth0_ErrorManager::insert_auth0_error(
__METHOD__ . ' L:' . __LINE__,
__( 'An oauth/token call triggered a 401 response from Auth0. ', 'wp-auth0' ) .
__( 'Please check the Client Secret saved in the Auth0 plugin settings. ', 'wp-auth0' )
);
throw new WP_Auth0_LoginFlowValidationException( __( 'Not Authorized', 'wp-auth0' ), $exchange_resp_code );

$decoded_token = JWT::decode(
$data->id_token,
$this->a0_options->get_client_secret_as_key(),
array( $this->a0_options->get_client_signing_algorithm() )
);
} else if ( empty( $exchange_resp_body ) ) {

$data->id_token = null;
$response = WP_Auth0_Api_Client::get_user(
$this->a0_options->get( 'domain' ),
WP_Auth0_Api_Client::get_client_token(),
$decoded_token->sub
);
// Unsuccessful for another reason
if ( $exchange_resp instanceof WP_Error ) {
WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' L:' . __LINE__, $exchange_resp );
}

if ( $response instanceof WP_Error ) {
WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => WP_Auth0_Api_Client::get_user()', $response );
throw new WP_Auth0_LoginFlowValidationException( __( 'Unknown error', 'wp-auth0' ), $exchange_resp_code );
}

error_log( $response->get_error_message() );
$data = json_decode( $exchange_resp_body );

throw new WP_Auth0_LoginFlowValidationException( );
}
if ( empty( $data->access_token ) ) {

$userinfo = json_decode( $response['body'] );
if ( $this->login_user( $userinfo, $data->id_token, $data->access_token ) ) {
if ( ! empty( $state_decoded->interim ) ) {
include WPA0_PLUGIN_DIR . 'templates/login-interim.php';
exit();
} else {
if ( ! empty( $state_decoded->redirect_to ) && wp_login_url() !== $state_decoded->redirect_to ) {
$redirectURL = $state_decoded->redirect_to;
} else {
$redirectURL = $this->a0_options->get( 'default_login_redirection' );
}
// Look for clues as to what went wrong
throw new WP_Auth0_LoginFlowValidationException(
! empty( $data->error_description ) ? $data->error_description : __( 'Unknown error', 'wp-auth0' ),
( ! empty( $data->error ) ? $data->error : $exchange_resp_code )
);
}

wp_safe_redirect( $redirectURL );
}
}
} elseif ( is_array( $response['response'] ) && 401 === (int) $response['response']['code'] ) {
$decoded_token = JWT::decode(
$data->id_token,
$this->a0_options->get_client_secret_as_key(),
array( $this->a0_options->get_client_signing_algorithm() )
);

$error = new WP_Error( '401', 'auth/token response code: 401 Unauthorized' );
$userinfo_resp = WP_Auth0_Api_Client::get_user(
$this->a0_options->get( 'domain' ),
WP_Auth0_Api_Client::get_client_token(),
$decoded_token->sub
);

WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => $this->login_user() = 401', $error );
$userinfo_resp_code = (int) wp_remote_retrieve_response_code( $userinfo_resp );
$userinfo_resp_body = wp_remote_retrieve_body( $userinfo_resp );

$msg = __( 'Error: the Client Secret configured on the Auth0 plugin is wrong. Make sure to copy the right one from the Auth0 dashboard.', 'wp-auth0' );
if ( $userinfo_resp instanceof WP_Error || 200 !== $userinfo_resp_code || empty( $userinfo_resp_body ) ) {

throw new WP_Auth0_LoginFlowValidationException( $msg );
} else {
$error = '';
$description = '';
// Management API call failed
// TODO: fallback to /userinfo
WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => WP_Auth0_Api_Client::get_user()', $userinfo_resp );
error_log( $userinfo_resp->get_error_message() );
throw new WP_Auth0_LoginFlowValidationException( __( 'Error getting user information', 'wp-auth0' ) );
}

if ( isset( $data->error ) ) {
$error = $data->error;
}
if ( isset( $data->error_description ) ) {
$description = $data->error_description;
}
$userinfo = json_decode( $userinfo_resp_body );

if ( ! empty( $error ) || ! empty( $description ) ) {
$error = new WP_Error( $error, $description );
WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__ . ' => $this->login_user()', $error );
if ( $this->login_user( $userinfo, $data->id_token, $data->access_token ) ) {
$state_decoded = $this->get_state();
if ( ! empty( $state_decoded->interim ) ) {
include WPA0_PLUGIN_DIR . 'templates/login-interim.php';
} else {
if ( ! empty( $state_decoded->redirect_to ) && wp_login_url() !== $state_decoded->redirect_to ) {
$redirectURL = $state_decoded->redirect_to;
} else {
$redirectURL = $this->a0_options->get( 'default_login_redirection' );
}
wp_safe_redirect( $redirectURL );
}
// Login failed!
wp_redirect( home_url() . '?message=' . $data->error_description );
exit();
}
exit();
}

/**
* Secondary login flow, Implicit Grant
* Client should be of type "Single Page App" for this flow
*
* @throws WP_Auth0_BeforeLoginException
* @throws WP_Auth0_LoginFlowValidationException
*
* @see https://auth0.com/docs/api-auth/tutorials/implicit-grant
*/
public function implicit_login() {

$token = $_POST['token'];
Expand All @@ -306,7 +301,7 @@ public function implicit_login() {

// validate that this JWT was made for us
if ( $this->a0_options->get( 'client_id' ) !== $decodedToken->aud ) {
throw new Exception( 'This token is not intended for us.' );
throw new WP_Auth0_LoginFlowValidationException( 'This token is not intended for us.' );
}

$decodedToken->user_id = $decodedToken->sub;
Expand Down Expand Up @@ -336,12 +331,17 @@ public function implicit_login() {
}
}

// Does all actions required to log the user in to wordpress, invoking hooks as necessary
// $user (stdClass): the WP user object, such as returned by get_user_by(...)
// $user_profile (stdClass): the Auth0 profile of the user
// $is_new (boolean): `true` if the user was created on Wordress, `false` if not. Don't get confused with Auth0 registrations, this flag will tell you if a new user was created on the WordPress database.
// $id_token (string): the user's JWT
// $access_token (string): the user's access token. It is not provided when using the **Implicit flow**.
/**
* Does all actions required to log the user in to wordpress, invoking hooks as necessary
*
* @param object $user - the WP user object, such as returned by get_user_by()
* @param object $userinfo - the Auth0 profile of the user
* @param bool $is_new - `true` if the user was created in the WordPress database, `false` if not
* @param string $id_token - user's ID token returned from Auth0
* @param string $access_token - user's access token returned from Auth0; not provided when using implicit_login()
*
* @throws WP_Auth0_BeforeLoginException
*/
private function do_login( $user, $userinfo, $is_new, $id_token, $access_token ) {
$remember_users_session = $this->a0_options->get( 'remember_users_session' );

Expand All @@ -356,35 +356,29 @@ private function do_login( $user, $userinfo, $is_new, $id_token, $access_token )

$secure_cookie = is_ssl();

/**
* Filters whether to use a secure sign-on cookie.
*
* @since 3.1.0
*
* @param bool $secure_cookie Whether to use a secure sign-on cookie.
* @param array $credentials {
* Array of entered sign-on data.
*
* @type string $user_login Username.
* @type string $user_password Password entered.
* @type bool $remember Whether to 'remember' the user. Increases the time
* that the cookie will be kept. Default false.
* }
*/
// See wp_signon() for documentation on this filter
$secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, array(
"user_login" => $user->user_login,
"user_password" => null,
"remember" => $remember_users_session
)
);

//wp_set_current_user( $user->ID, $user->user_login );
wp_set_auth_cookie( $user->ID, $remember_users_session, $secure_cookie);
do_action( 'wp_login', $user->user_login, $user );
do_action( 'auth0_user_login' , $user->ID, $userinfo, $is_new, $id_token, $access_token );
}

// return true if login was successful, false otherwise
/**
* @param object $userinfo - the Auth0 profile of the user
* @param string $id_token - user's ID token returned from Auth0
* @param string $access_token - user's access token returned from Auth0; not provided when using implicit_login()
*
* @return bool
*
* @throws WP_Auth0_BeforeLoginException
* @throws WP_Auth0_LoginFlowValidationException
*/
public function login_user( $userinfo, $id_token, $access_token ) {
// If the userinfo has no email or an unverified email, and in the options we require a verified email
// notify the user he cant login until he does so.
Expand Down Expand Up @@ -473,10 +467,6 @@ public function login_user( $userinfo, $id_token, $access_token ) {
} catch ( WP_Auth0_EmailNotVerifiedException $e ) {
WP_Auth0_Email_Verification::render_die( $e->userinfo );
}
// catch ( Exception $e ) {
// echo $e;exit;
// }

return true;
}
}
Expand Down

0 comments on commit 46e20ec

Please sign in to comment.