From 0f63a3fff1664b8f6fca6c036f3df1b4b6def41c Mon Sep 17 00:00:00 2001 From: Josh Cunningham Date: Fri, 4 May 2018 05:38:42 -0700 Subject: [PATCH] New class for state handling; set cookie for implicit nonce --- WP_Auth0.php | 5 - assets/js/lock-init.js | 4 + lib/WP_Auth0_Lock10_Options.php | 6 +- lib/WP_Auth0_Lock_Options.php | 2 +- lib/WP_Auth0_LoginManager.php | 73 ++++---------- lib/WP_Auth0_Nonce_Handler.php | 173 ++++++++------------------------ lib/WP_Auth0_Random_Storage.php | 132 ++++++++++++++++++++++++ lib/WP_Auth0_State_Handler.php | 72 +++++++++++++ templates/login-form.php | 3 +- 9 files changed, 272 insertions(+), 198 deletions(-) create mode 100644 lib/WP_Auth0_Random_Storage.php create mode 100644 lib/WP_Auth0_State_Handler.php diff --git a/WP_Auth0.php b/WP_Auth0.php index ed672ae3..8900447e 100644 --- a/WP_Auth0.php +++ b/WP_Auth0.php @@ -23,7 +23,6 @@ define( 'WPA0_AUTH0_LOGIN_FORM_ID', 'auth0-login-form' ); define( 'WPA0_CACHE_GROUP', 'wp_auth0' ); -define( 'WPA0_STATE_COOKIE_NAME', 'auth0_state' ); define( 'WPA0_JWKS_CACHE_TRANSIENT_NAME', 'WP_Auth0_JWKS_cache' ); define( 'WPA0_LANG', 'wp-auth0' ); // deprecated; do not use for translations @@ -127,10 +126,6 @@ public function init() { $this->check_signup_status(); - if ( $this->a0_options->get( 'auto_login' ) ) { - WP_Auth0_Nonce_Handler::getInstance()->setCookie(); - } - WP_Auth0_Email_Verification::init(); } diff --git a/assets/js/lock-init.js b/assets/js/lock-init.js index d69ab921..4e90dfa4 100644 --- a/assets/js/lock-init.js +++ b/assets/js/lock-init.js @@ -21,6 +21,10 @@ jQuery(document).ready(function ($) { // Set state cookie to verify during callback Cookies.set( opts.stateCookieName, opts.settings.auth.params.state ); + if ( opts.settings.auth.params.nonce ) { + Cookies.set( opts.nonceCookieName, opts.settings.auth.params.nonce ); + } + // Set Lock to standard or Passwordless var Lock = opts.usePasswordless ? new Auth0LockPasswordless( opts.clientId, opts.domain, opts.settings ) diff --git a/lib/WP_Auth0_Lock10_Options.php b/lib/WP_Auth0_Lock10_Options.php index 18305113..b74c7123 100644 --- a/lib/WP_Auth0_Lock10_Options.php +++ b/lib/WP_Auth0_Lock10_Options.php @@ -55,7 +55,7 @@ public function get_state_obj( $redirect_to = null ) { $stateObj = array( 'interim' => ( isset( $_GET['interim-login'] ) && $_GET['interim-login'] == 1 ), - 'nonce' => WP_Auth0_Nonce_Handler::getInstance()->get() + 'nonce' => WP_Auth0_State_Handler::getInstance()->getUniqid() ); if ( !empty( $redirect_to ) ) { @@ -154,7 +154,7 @@ public function get_sso_options() { unset( $options["authParams"] ); $options["state"] = $this->get_state_obj( $redirect_to ); - $options["nonce"] = WP_Auth0_Nonce_Handler::getInstance()->get(); + $options["nonce"] = WP_Auth0_Nonce_Handler::getInstance()->getUniqid(); return $options; } @@ -182,7 +182,7 @@ public function get_lock_options() { $extraOptions["auth"]["responseType"] = 'id_token'; $extraOptions["auth"]["redirectUrl"] = $this->get_implicit_callback_url(); $extraOptions["autoParseHash"] = false; - $extraOptions["auth"]["params"]["nonce"] = WP_Auth0_Nonce_Handler::getInstance()->get(); + $extraOptions["auth"]["params"]["nonce"] = WP_Auth0_Nonce_Handler::getInstance()->getUniqid(); } else { $extraOptions["auth"]["responseType"] = 'code'; $extraOptions["auth"]["redirectUrl"] = $this->get_code_callback_url(); diff --git a/lib/WP_Auth0_Lock_Options.php b/lib/WP_Auth0_Lock_Options.php index 441c93fa..91898db8 100644 --- a/lib/WP_Auth0_Lock_Options.php +++ b/lib/WP_Auth0_Lock_Options.php @@ -110,7 +110,7 @@ public function modal_button_name() { public function get_state_obj( $redirect_to = null ) { $stateObj = array( 'interim' => ( isset( $_GET['interim-login'] ) && $_GET['interim-login'] == 1 ), - 'nonce' => WP_Auth0_Nonce_Handler::getInstance()->get() + 'nonce' => WP_Auth0_Nonce_Handler::getInstance()->getUniqid() ); if ( !empty( $redirect_to ) ) { $stateObj["redirect_to"] = addslashes( $redirect_to ); diff --git a/lib/WP_Auth0_LoginManager.php b/lib/WP_Auth0_LoginManager.php index a7985909..77e448f2 100755 --- a/lib/WP_Auth0_LoginManager.php +++ b/lib/WP_Auth0_LoginManager.php @@ -42,22 +42,6 @@ class WP_Auth0_LoginManager { */ protected $users_repo; - /** - * State value returned from successful Auth0 login. - * - * @var string - * - * @see WP_Auth0_Lock10_Options::get_state_obj() - */ - protected $state; - - /** - * Decoded version of $this>state. - * - * @var object - */ - protected $state_decoded; - /** * WP_Auth0_LoginManager constructor. * @@ -127,7 +111,12 @@ public function login_auto() { $auth_url = 'https://' . $this->a0_options->get( 'domain' ) . '/authorize'; $auth_url = add_query_arg( array_map( 'rawurlencode', $auth_params ), $auth_url ); - setcookie( WPA0_STATE_COOKIE_NAME, $auth_params['state'], time() + WP_Auth0_Nonce_Handler::COOKIE_EXPIRES, '/' ); + WP_Auth0_State_Handler::getInstance()->setStateCookie( $auth_params['state'] ); + + if ( isset( $auth_params['nonce'] ) ) { + WP_Auth0_Nonce_Handler::getInstance()->setCookie(); + } + wp_redirect( $auth_url ); exit; } @@ -154,7 +143,7 @@ public function init_auth0() { // Check for valid state nonce, set in WP_Auth0_Lock10_Options::get_state_obj(). // See https://auth0.com/docs/protocols/oauth2/oauth-state for more info. - if ( ! $this->validate_state() ) { + if ( ! WP_Auth0_State_Handler::getInstance()->validate() ) { $this->die_on_login( __( 'Invalid state', 'wp-auth0' ) ); } @@ -268,7 +257,7 @@ public function redirect_login() { $userinfo = json_decode( $userinfo_resp_body ); if ( $this->login_user( $userinfo, $data->id_token, $data->access_token ) ) { - $state_decoded = $this->get_state( true ); + $state_decoded = $this->get_state(); if ( ! empty( $state_decoded->interim ) ) { include WPA0_PLUGIN_DIR . 'templates/login-interim.php'; } else { @@ -336,7 +325,7 @@ public function implicit_login() { if ( $this->login_user( $decoded_token, $token, null ) ) { // Validated above in $this->init_auth0(). - $state_decoded = $this->get_state( true ); + $state_decoded = $this->get_state(); if ( ! empty( $state_decoded->interim ) ) { include WPA0_PLUGIN_DIR . 'templates/login-interim.php'; @@ -577,8 +566,8 @@ public function auth0_singlelogout_footer() { /** * End the PHP session. - * - * TODO: Deprecate + * + * TODO: Deprecate */ public function end_session() { if ( session_id() ) { @@ -612,7 +601,7 @@ public static function get_authorize_params( $connection = null, $redirect_to = $options = WP_Auth0_Options::Instance(); $lock_options = new WP_Auth0_Lock10_Options(); $is_implicit = (bool) $options->get( 'auth0_implicit_workflow', FALSE ); - $nonce = WP_Auth0_Nonce_Handler::getInstance()->get(); + $nonce = WP_Auth0_Nonce_Handler::getInstance()->getUniqid(); $params[ 'client_id' ] = $options->get( 'client_id' ); $params[ 'scope' ] = self::get_userinfo_scope( 'authorize_url' ); @@ -671,41 +660,13 @@ protected function query_vars( $key ) { /** * Get the state value returned from Auth0 during login processing. * - * @param bool $decoded - pass `true` to return decoded state, leave blank for raw string. - * * @return string|object|null */ - protected function get_state( $decoded = false ) { - - if ( empty( $this->state ) ) { - // Get and store base64 encoded state. - $state_val = isset( $_REQUEST['state'] ) ? $_REQUEST['state'] : ''; - $state_val = urldecode( $state_val ); - $this->state = $state_val; - - // Decode and store the state. - $state_val = base64_decode( $state_val ); - $this->state_decoded = json_decode( $state_val ); - } - - if ( $decoded ) { - return is_object( $this->state_decoded ) ? $this->state_decoded : null; - } else { - return $this->state; - } - } - - /** - * Check the state send back from Auth0 with the one stored in the user's browser. - * - * @return bool - */ - protected function validate_state() { - $valid = isset( $_COOKIE[ WPA0_STATE_COOKIE_NAME ] ) - ? $_COOKIE[ WPA0_STATE_COOKIE_NAME ] === $this->get_state() - : false; - setcookie( WPA0_STATE_COOKIE_NAME, '', 0, '/' ); - return $valid; + protected function get_state() { + $state_val = $this->query_vars( 'state' ); + $state_val = base64_decode( $state_val ); + $state_val = json_decode( $state_val ); + return $state_val; } /** diff --git a/lib/WP_Auth0_Nonce_Handler.php b/lib/WP_Auth0_Nonce_Handler.php index d3fcb117..86689f8a 100644 --- a/lib/WP_Auth0_Nonce_Handler.php +++ b/lib/WP_Auth0_Nonce_Handler.php @@ -1,134 +1,43 @@ init(); - } - - /** - * Private to prevent cloning - */ - private function __clone() {} - - /** - * Private to prevent serializing - */ - private function __sleep() {} - - /** - * Private to prevent unserializing - */ - private function __wakeup() {} - - /** - * Start-up process to make sure we have a nonce stored - */ - private function init() { - if ( isset( $_COOKIE[ self::COOKIE_NAME ] ) ) { - // Have a nonce cookie, don't want to generate a new one - $this->_uniqid = $_COOKIE[ self::COOKIE_NAME ]; - } else { - // No nonce cookie, need to create one - $this->_uniqid = $this->generateNonce(); - } - } - - /** - * Get the internal instance of the singleton - * - * @return WP_Auth0_Nonce_Handler - */ - public static final function getInstance() { - if ( null === self::$_instance ) { - self::$_instance = new WP_Auth0_Nonce_Handler(); - } - return self::$_instance; - } - - /** - * Return the unique ID used for nonce validation - * - * @return string - */ - public function get() { - return $this->_uniqid; - } - - /** - * Check if the stored nonce matches a specific value - * - * @param string $nonce - the nonce to validate against the stored value - * - * @return bool - */ - public function validate( $nonce ) { - $valid = isset( $_COOKIE[ self::COOKIE_NAME ] ) ? $_COOKIE[ self::COOKIE_NAME ] === $nonce : FALSE; - $this->reset(); - return $valid; - } - - /** - * Set the nonce cookie value - * - * @return bool - */ - public function setCookie() { - $_COOKIE[ self::COOKIE_NAME ] = $this->_uniqid; - return setcookie( self::COOKIE_NAME, $this->_uniqid, time() + self::COOKIE_EXPIRES, '/' ); - } - - /** - * Reset the nonce cookie value - * - * @return bool - */ - public function reset() { - return setcookie( self::COOKIE_NAME, '', 0 ); - } - - /** - * Generate a random ID - * If using on PHP 7, it will be cryptographically secure - * - * @see https://secure.php.net/manual/en/function.random-bytes.php - * - * @param int $bytes - number of bytes to generate - * - * @return string - */ - public function generateNonce( $bytes = 32 ) { - $nonce_bytes = function_exists( 'random_bytes' ) ? random_bytes( $bytes ) : openssl_random_pseudo_bytes( $bytes ); - return bin2hex( $nonce_bytes ); - } -} \ No newline at end of file +final class WP_Auth0_Nonce_Handler extends WP_Auth0_Random_Storage { + + /** + * Cookie name used to store nonce + * + * @var string + */ + const UNIQID_COOKIE_NAME = 'auth0_nonce_uniqid'; + + /** + * Singleton class instance + * + * @var WP_Auth0_Nonce_Handler|null + */ + private static $_instance = null; + + /** + * Get the internal instance of the singleton + * + * @return WP_Auth0_Nonce_Handler + */ + public static final function getInstance() { + if ( null === static::$_instance ) { + static::$_instance = new static; + } + return static::$_instance; + } + + /** + * Check if the stored nonce matches a specific value + * + * @param string $nonce - the nonce to validate against the stored value + * + * @return bool + */ + public function validate( $nonce ) { + $valid = isset( $_COOKIE[ self::UNIQID_COOKIE_NAME ] ) ? $_COOKIE[ self::UNIQID_COOKIE_NAME ] === $nonce : FALSE; + $this->reset(); + return $valid; + } +} diff --git a/lib/WP_Auth0_Random_Storage.php b/lib/WP_Auth0_Random_Storage.php new file mode 100644 index 00000000..bc905a75 --- /dev/null +++ b/lib/WP_Auth0_Random_Storage.php @@ -0,0 +1,132 @@ +init(); + } + + /** + * Private to prevent cloning + */ + private function __clone() {} + + /** + * Private to prevent serializing + */ + private function __sleep() {} + + /** + * Private to prevent unserializing + */ + private function __wakeup() {} + + /** + * Start-up process to make sure we have something stored + */ + private function init() { + if ( isset( $_COOKIE[ static::UNIQID_COOKIE_NAME ] ) ) { + // Have a cookie, don't want to generate a new one + $this->_uniqid = $_COOKIE[ static::UNIQID_COOKIE_NAME ]; + } else { + // No cookie, need to create one + $this->_uniqid = $this->generateNonce(); + } + } + + /** + * Get the internal instance of the singleton + * + * @return WP_Auth0_Nonce_Handler + */ + public static function getInstance() { + if ( null === static::$_instance ) { + static::$_instance = new static; + } + return static::$_instance; + } + + /** + * Return the unique ID used for validation + * + * @return string + */ + public function getUniqid() { + return $this->_uniqid; + } + + /** + * Return the cookie expiration time to set. + * + * @return integer + */ + public function getCookieExp() { + return time() + self::COOKIE_EXPIRES; + } + + /** + * Set the random storage cookie. + * + * @return bool + */ + public function setCookie() { + $_COOKIE[ static::UNIQID_COOKIE_NAME ] = $this->_uniqid; + return setcookie( static::UNIQID_COOKIE_NAME, $this->_uniqid, $this->getCookieExp(), '/' ); + } + + /** + * Reset the cookie value + * + * @return bool + */ + public function reset() { + return setcookie( static::UNIQID_COOKIE_NAME, '', 0 ); + } + + /** + * Generate a random ID + * If using on PHP 7, it will be cryptographically secure + * + * @see https://secure.php.net/manual/en/function.random-bytes.php + * + * @param int $bytes - number of bytes to generate + * + * @return string + */ + public function generateNonce( $bytes = 32 ) { + $nonce_bytes = function_exists( 'random_bytes' ) ? random_bytes( $bytes ) : openssl_random_pseudo_bytes( $bytes ); + return bin2hex( $nonce_bytes ); + } +} \ No newline at end of file diff --git a/lib/WP_Auth0_State_Handler.php b/lib/WP_Auth0_State_Handler.php new file mode 100644 index 00000000..748edb7e --- /dev/null +++ b/lib/WP_Auth0_State_Handler.php @@ -0,0 +1,72 @@ +getState() + : FALSE; + $this->reset(); + return $valid; + } + + /** + * Set the state cookie value. + * + * @param string $value - state value to store, same as what is sent to Auth0. + * + * @return bool + */ + public function setStateCookie( $value ) { + $this->setCookie(); + $_COOKIE[ self::STATE_COOKIE_NAME ] = $value; + return setcookie( self::STATE_COOKIE_NAME, $value, $this->getCookieExp(), '/' ); + } + + /** + * Get the returned state from a URL parameter. + * + * @return string + */ + public function getState() { + return isset( $_REQUEST['state'] ) ? urldecode( $_REQUEST['state'] ) : ''; + } +} diff --git a/templates/login-form.php b/templates/login-form.php index aab42514..54c419f7 100755 --- a/templates/login-form.php +++ b/templates/login-form.php @@ -21,7 +21,8 @@ function renderAuth0Form( $canShowLegacyLogin = true, $specialSettings = array() 'ready' => WP_Auth0::ready(), 'domain' => $options->get( 'domain' ), 'clientId' => $options->get( 'client_id' ), - 'stateCookieName' => WPA0_STATE_COOKIE_NAME, + 'stateCookieName' => WP_Auth0_State_Handler::UNIQID_COOKIE_NAME, + 'nonceCookieName' => WP_Auth0_Nonce_Handler::UNIQID_COOKIE_NAME, 'usePasswordless' => $use_passwordless, 'loginFormId' => WPA0_AUTH0_LOGIN_FORM_ID, 'showAsModal' => ! empty( $specialSettings['show_as_modal'] ),