Support » Plugin: WP API SwaggerUI » Basic Auth enforced for all

  • Resolved brianP6

    (@brianp6)


    Hi,
    This plugin is superb!
    What an excellent way to get Swagger API documentation into WP. Really good!

    One problem though …
    The Basic Auth works great for testing out the API, but with the plugin is enabled, every REST call is authenticated whether it is part of the API or not (e.g. standard WP ones and some others that I’ve built). End points that don’t require authentication get delivered a failure ……
    {
    “code”: “empty_username”,
    “message”: “ERROR: The username field is empty.”,
    “data”: null,
    “additional_errors”: [
    {
    “code”: “empty_password”,
    “message”: “ERROR: The password field is empty.”,
    “data”: null
    }
    ]
    }

    These endpoints should be secured by the ‘permission callback’ but with Basic Auth enforced it never gets that far..

    It also breaks my WordFence install from communicating with WordFence Central.

    For me personally, I’d prefer it if the Basic Auth was used but not enforced. That way my permission_callback can check that the user is logged in (and the correct role) before allowing it to go any further. My API is secured with Oauth2 in normal use to ensure this.

    Well done on the plugin, I can see this being used more and more as REST in WP picks up.

    Many thanks,
    Brian.

Viewing 8 replies - 1 through 8 (of 8 total)
  • OK I’ve done some digging.
    It seems that my server has REDIRECT_HTTP_AUTHORIZATION without any value which is throwing the logic to try to take that non-existent value and turn it into a username/password.

    A simple fix would be to check for not empty after checking for the existence of the Server parameter in swaggerauth.php:
    if ( $server->has( ‘REDIRECT_HTTP_AUTHORIZATION’ ) && !empty($server->get(‘REDIRECT_HTTP_AUTHORIZATION’))) {

    Hope you can include this fix (or similar) in your next release so that I don’t get wiped out on the next update ๐Ÿ™‚

    Many many thanks,
    Brian.

    • This reply was modified 1 year, 1 month ago by brianP6.
    Plugin Author agussuroyo

    (@agussuroyo)

    Thanks Brian for your feedback ๐Ÿ™‚

    I’ve put your code on version 1.0.2, you can check it.

    Thanks

    Thank you Agus that works ๐Ÿ™‚

    Is there any way to pick up a schema or other more textual documentation that your swagger api can use?
    It would be good to know before I start writing my knowledge base articles on my new REST API ๐Ÿ™‚

    Cheers,
    Brian.

    Plugin Author agussuroyo

    (@agussuroyo)

    Hi Brian

    The SwaggerUI (https://swagger.io/tools/swagger-ui/) is visualize OpenAPI 3.0.
    Actually my plugin is just doing one process, that’s generate json schema for SwaggerUI, so SwaggerUI can render it

    Thanks

    Hey,

    I’ve been using this awesome plugin lately while developing a custom endpoint in the API for a WooCommerce based site, and discovered that it prevents basic authentication to working using the WooCommerce consumer key/secret (as the username/password combination) , which is possible when this plugin is disabled.

    To overcome that, I opted to edit the swaggerauth.php and updated the login handler to also try authentication using consumer key/secret method, similar to how the original woocommerce/includes/class-wc-rest-authentication.php file does this.

    Here’s how the updated swaggerauth.php file looked:

    <?php
    
    class SwaggerAuth {
    
    	private $error = null;
    
    	// woocommerce/includes/class-wc-rest-authentication.php -> get_user_data_by_consumer_key function
    	private function get_user_data_by_consumer_key( $consumer_key ) {
    		global $wpdb;
    
    		$consumer_key = wc_api_hash( sanitize_text_field( $consumer_key ) );
    		$user         = $wpdb->get_row(
    			$wpdb->prepare(
    				"
    			SELECT key_id, user_id, permissions, consumer_key, consumer_secret, nonces
    			FROM {$wpdb->prefix}woocommerce_api_keys
    			WHERE consumer_key = %s
    		",
    				$consumer_key
    			)
    		);
    
    		return $user;
    	}
    
    	public function handler( $user_id ) {
    		// Don't authenticate twice
    		if ( ! empty( $user_id ) ) {
    			return $user_id;
    		}
    
    		$server = new SwaggerBag( $_SERVER );
    
    		// Check that we're trying to authenticate
    		if ( ! $server->has( 'PHP_AUTH_USER' ) ) {
    			
    			$user_pass = $server->get( 'REDIRECT_HTTP_AUTHORIZATION' );
    			if ( $server->has( 'REDIRECT_HTTP_AUTHORIZATION' ) && ! empty( $user_pass )  ) {
    				list($username, $password) = explode( ':', base64_decode( substr( $user_pass, 6 ) ) );
    				$server->set( 'PHP_AUTH_USER', $username );
    				$server->set( 'PHP_AUTH_PW', $password );
    			} else {
    				return $user_id;
    			}
    		}
    
    		$username	 = $server->get( 'PHP_AUTH_USER' );
    		$password	 = $server->get( 'PHP_AUTH_PW' );
    		/**
    		 * In multi-site, wp_authenticate_spam_check filter is run on authentication. This filter calls
    		 * get_currentuserinfo which in turn calls the determine_current_user filter. This leads to infinite
    		 * recursion and a stack overflow unless the current function is removed from the determine_current_user
    		 * filter during authentication.
    		 */
    		remove_filter( 'determine_current_user', [ $this, 'handler' ], 20 );
    
    		$user = wp_authenticate( $username, $password );
    
    		add_filter( 'determine_current_user', [ $this, 'handler' ], 20 );
    
    		if ( is_wp_error( $user ) ) {
    
    			// If WooCommerce enabled, try using consumer key/secret to authenticate
    			if (class_exists( 'woocommerce' )) {
    				$this->user = $this->get_user_data_by_consumer_key( $username );
    				if ( empty( $this->user ) ) {
    					$this->error = $user;
    					return null;
    				}
    
    				// Validate user secret.
    				if ( ! hash_equals( $this->user->consumer_secret, $password ) ) { // @codingStandardsIgnoreLine
    					$this->error = $user;
    					return null;
    				}
    			} else {
    				$this->error = $user;
    				return null;
    			}
    
    		}
    
    		$this->error = true;
    
    		return $user->ID;
    	}
    
    	public function error( $error ) {
    
    		if ( ! empty( $error ) ) {
    			return $error;
    		}
    
    		return $this->error;
    	}
    
    	public function appendSwaggerAuth( $auth ) {
    		if ( ! is_array( $auth ) ) {
    			$auth = [];
    		}
    
    		$auth['basic'] = array(
    			'type' => 'basic'
    		);
    
    		return $auth;
    	}
    
    }
    
    $basic = new SwaggerAuth();
    
    add_filter( 'determine_current_user', [ $basic, 'handler' ] );
    add_filter( 'rest_authentication_errors', [ $basic, 'error' ] );
    add_filter( 'swagger_api_security_definitions', [ $basic, 'appendSwaggerAuth' ] );
    
    

    You can also view a gist of this here: https://gist.github.com/ouija/0d111feed63bfaa118a0d3ecd20666d0

    Hope this helps anyone else looking to achieve something similar.

    UPDATE: The code above has since been modified slightly to properly return the user ID when authenticating via a consumer key/secret, and has also been updated to work with consumer_key / consumer_secret GET parameters being passed to use as authentication, and has been updated to match the plugin’s current version changes regaring priorities. See the updated gist here: https://gist.github.com/ouija/0d111feed63bfaa118a0d3ecd20666d0

    • This reply was modified 9 months, 2 weeks ago by ouija.
    Plugin Author agussuroyo

    (@agussuroyo)

    Hi @ouija

    I’m really appreciate for your help, I’ve made some changes but little bit different with yours, but it should be ok.

    Please let me know if you found something weird or anything else

    Thanks

    @agussuroyo Just updated to the latest version and all is working well! Thank you for integrating those changes into the plugin, it’s really appreciated!

    Only thing I would note is that *if* someone wanted to enable authentication via the “consumer_key” and “consumer_secret” GET variables (like how can be done with the default WooCommerce API endpoint, but is not a secure or preferred method of authentication by any means other than testing), this can be done by adding the following to the swaggerauth.php file, after $server = new SwaggerBag( $_SERVER ); at line 13:

    
    // Check if consumer_key/secret get variables are being passed and use those to authenticate
    if ( ! empty( $_GET['consumer_key'] ) && ! empty( $_GET['consumer_secret'] ) ) {
    	$server->set( 'PHP_AUTH_USER', $_GET['consumer_key'] );
    	$server->set( 'PHP_AUTH_PW', $_GET['consumer_secret'] );
    }
    

    Again, this is minor and shouldn’t be used in a production environment as a means of authentication, but just wanted to note that it can be achieved as so.

    Thanks again for such a great and helpful plugin!

Viewing 8 replies - 1 through 8 (of 8 total)
  • The topic ‘Basic Auth enforced for all’ is closed to new replies.