How We Improved Composer Support For Our Premium Plugins Again

#

Composer is my go-to method of managing free plugins in WordPress sites. However, managing premium plugins is a little more tricky. Last year we introduced full Composer support for our premium WordPress plugins which was generally well received, although a number of people questioned our approach with the new Composer key authentication method.

We had decided to use a new Composer-only key so that your actual plugin license key didn’t have to be used, shared with team members, or stored in version control. However, when a few folks mentioned other existing Composer authentication techniques it became clear our solution wasn’t perfect and we decided to do something about it.

TL;DR We now support basic authentication for our Composer repository, which means your credentials can sit in an auth.json file and be kept out of version control. 🎉

Banderas gif

How it Works

When you visit Settings in My Account, you will see that any existing Composer keys now have both username and password keys instead of just a single key. When adding the Delicious Brains repository to your composer.json you no longer need to add the key in the URL, it’s as simple as this:

"repositories": [
    {
        "type": "composer",
         "url": "https://composer.deliciousbrains.com"
    }
]

The auth.json file contains the key and password to be used as basic authentication credentials when connecting to our repository:

{
    "http-basic": {
        "composer.deliciousbrains.com": {
            "username": "50BDFC1B7176DFED785036AA2BFB27DE",
            "password": "385347081A5F39AAC46564B6428DEBBC"
        }
    }
}

The auth.json file should be placed beside the composer.json file in your project on the server. This means it doesn’t need to be committed to version control and can be added to your .gitignore file. Alternatively you could add the credentials to an existing auth.json located in your COMPOSER_HOME directory.

Each Composer key set has an auth.json file ready to be used. You can now download the JSON files with a handy download button in the bottom right-hand corner:

Composer auth.json new UI

We have new documentation for the whole process of installing our plugins with Composer here.

The Science Bit

For the technical implementation there wasn’t a whole lot of work needed. We added a new column for the password to our custom table that stores the Composer keys. The passwords were then automatically populated for existing keys using an MD5 hash.

Although this authentication method is using basic authentication, we don’t actually want to protect our composer.deliciousbrains.com subdomain with it, we just need PHP to use the credentials to validate if the user has an active license for the plugin for which Composer is requesting the download.

This meant some Nginx magic which I can’t take any credit for. Ashley helped configure the server adding the following so Nginx passed the http authorization header to PHP.

fastcgi_param COMPOSER_AUTH $http_authorization;

We chose to name the header COMPOSER_AUTH so it’s clear what $_SERVER variable we needed to access. The credentials are passed as a base64 encoded string with a ‘Basic ‘ string prefix, so our validation PHP method now looks like:

protected function validate_composer_auth_authentication() {
    $auth = filter_input( INPUT_SERVER, 'COMPOSER_AUTH', FILTER_SANITIZE_STRING );

    if ( empty( $auth ) ) {
        return false;
    }

    $auth = str_replace( 'Basic ', '', $auth );
    $auth = explode( ':', base64_decode( $auth ) );

    if ( ! isset( $auth[0] ) || ! isset( $auth[1] ) ) {
        return false;
    }

    $user = $auth[0];
    $pass = $auth[1];

    if ( ! \WC_Software_Composer::is_composer_key_secret_valid( $user, $pass ) ) {
        $this->http_error_header = 'HTTP/1.1 404 API Key Not Found';
        $this->output_errors( array( 'composer_key_not_found' => __( 'Composer API key not found.', 'delicious-brains-api' ) ) );
    }

    return $user;
}

Once the key and secret are extracted from the string we check them to see if they exist for a customer with an active license for the plugin.

We also needed to add a new Nginx rewrite to catch requests to composer.deliciousbrains.com/packages.json without the Composer key in the URL:

rewrite ^/packages\.json$ /index.php?wc-api=delicious-brains&request=composer_packages last;

This rewrite is after the existing one so we can still catch requests using the old method.

Backwards Compatibility

But what about existing Composer projects using the old method? Don’t worry, in true WordPress fashion we are maintaining backwards compatibility. If the above validate_composer_auth_authentication method returns false then we fallback to the old way and check for the existence of the key in the repository URL:

/**
* Endpoint for downloading Composer packages.
*/
public function request_composer_download() {
    $key = $this->validate_composer_auth_authentication();

    if ( ! $key ) {
        $key = $this->validate_legacy_composer_key_authentication();
    }

    if ( empty( $_GET['package'] ) ) {
        $this->http_error_header = 'HTTP/1.1 400 Bad Request';
        $this->output_errors( array( 'no_package_name' => __( 'You did not provide a package name.', 'delicious-brains-api' ) ) );
    }

    if ( empty( $_GET['version'] ) ) {
        $this->http_error_header = 'HTTP/1.1 400 Bad Request';
        $this->output_errors( array( 'no_package_version' => __( 'You did not provide a package version.', 'delicious-brains-api' ) ) );
    }
    
    # Process the download of the plugin
   …
}

The documentation for the old authentication method can be found here.

Wrapping Up

We believe this now ticks all the boxes of delivering Composer support for our premium plugins, and I am looking forward to using auth.json files in the future and keeping my keys out of git.

I hope other WordPress plugin shops and developers are beginning to add better Composer support for their products. Do you know of any that have added support? Will you be using our new method? Let us know in the comments.

About the Author

Iain Poulson

Iain is a WordPress and PHP developer from England. He builds free and premium plugins, as well as occasionally blogging about WordPress. Moonlights as a PhpStorm evangelist.