CakePHP and connecting to GitHub API

I had to write a small tool to automate releasing certain GitHub repositories, and for that to authenticate I had to connect to GitHub API.
The integration wasn’t super-easy as there was no documentation yet on how this could be done. But I finally figured it out and want to share it.

HybridAuth plugin

I first introduce the plugin pretty quick I chose to use as authentication piece.
HybridAuth is maintained by a CakePHP core developer and bridges the original HybridAuth implementation into CakePHP. That library aims to "act as an abstract API between your application and various social APIs and identities providers". Out of the box it provides quite a few very popular services to connect to.

Getting started

I did install the plugin as documented, I also made sure the Migration file for it has been included because
we do need a "social_profiles" table here.

Then I connected the Users and SocialProfiles table:

    /**
     * @param array $config The configuration for the Table.
     *
     * @return void
     */
    public function initialize(array $config) {
        parent::initialize($config);
        ...

        $this->hasMany('ADmad/HybridAuth.SocialProfiles');
        EventManager::instance()->on('HybridAuth.newUser', [$this, 'createUser']);
    }

    /**
     * @param \Cake\Event\Event $event
     *
     * @throws \RuntimeException
     *
     * @return \App\Model\Entity\User
     */
    public function createUser(Event $event) {
        // Entity representing record in social_profiles table
        $profile = $event->data()['profile'];
        $username = substr($profile->profile_url, strrpos($profile->profile_url, '/') + 1);
        $user = $this->newEntity(
            [
                'username' => $username,
                'email' => $profile->email
            ]
        );
        $result = $this->save($user);
        if (!$result) {
            throw new \RuntimeException('Unable to save new user:' . print_r($user->errors(), true));
        }
        return $result;
    }

I used the "profile_url" data to automatically generate the same user on my website.
Since the login was only allowed via GitHub login, there was no change of collision.

Then I made sure the HybridAuth authentication adapter is added to the list of components in the AppController:

    /*
     * @return \Cake\Network\Response|null|void
     */
    public function initialize() {
        parent::initialize();
        ...

        $this->loadComponent('TinyAuth.Auth', [
            'authenticate' => [
                'Form',
                'ADmad/HybridAuth.HybridAuth',
            ],
        ]);
    }

I also modified the login according to the documentation.

And finally I just needed a link in the navigation menu in the case the user is not logged in yet:

echo $this->Html->link(
    'Login with GitHub',
    ['plugin' => false, 'prefix' => false, 'controller' => 'Account', 'action' => 'login', 
        '?' => ['provider' => 'Github', 'redirect' => $this->request->query('redirect')]
    ]
);

Note that the "redirect" query string is only necessary for CakePHP 3.4+ when the session is not used anymore for remembering the location to redirect to after login. And also note that at this point only a "dev" branch of the plugin supports the 3.4+ version yet.

Figuring out the configuration

Now that was the most difficult part. With a lot of debugging I found out that since the GitHub provider is not one of the core ones I need to provider wrapper path and class here:

    'HybridAuth' => [
        'providers' => [
            'Github' => [
                'enabled' => true,
                'keys' => [
                    'id' => env('AUTH_ID_GITHUB', ''),
                    'secret' => env('AUTH_SECRET_GITHUB', '')
                ],
                'wrapper' => [
                    'path' => ROOT . '/vendor/hybridauth/hybridauth/additional-providers/hybridauth-github/Providers/GitHub.php',
                    'class' => 'Hybrid_Providers_GitHub'
                ],
                'scope' => 'user:email,repo'
            ]
        ],
        'debug_mode' => false,
        'debug_file' => LOGS . 'hybridauth.log',
    ],

Note that I also set custom "scope" permissions here, you can however here leave that out or add more.

Small tweaks

I didn’t want to use the the plugin controller action to authenticate, but my own in AccountController (in order to execute a few custom things upon login).
So I just overwrote the hauth_return_toURL:

'hauth_return_to' => [
    'controller' => 'Account', 'action' => 'authenticated', 'plugin' => false, 'prefix' => false
]

Testing

Yeah, ok, here I did cheat.
The hybridauth library has a little flaw that makes it difficult to connect to CakePHP as plugin: It always forces the session to be started right away. Especially when testing the controllers now this can be super annoying as it throws ugly warnings:

..Warning Error: session_start(): Cannot send session cookie - headers already sent by 
(output started at phar:///home/vagrant/Apps/.../phpunit.phar/phpunit/Util/Printer.php:134) 
in [/home/vagrant/Apps/.../vendor/hybridauth/hybridauth/hybridauth/Hybrid/Storage.php, line 20]

So I just added the adapter when not in CLI mode:

if (PHP_SAPI !== 'cli') {
    $authenticate['ADmad/HybridAuth.HybridAuth'] = [
        ...
    ];
}
$this->Auth->config('authenticate', $authenticate);

Tests are green again 🙂

In short

All in all HybridAuth is a great CakePHP plugin to connect this HybridAuth library and any OpenID and OAuth authenticated service to your application.
Give it a spin!

Besides the here mentioned GitHub provider I also managed to use Facebook/Google sign-in this way in another app.
Basically all apps, if for technical users or more a social network, can benefit from such a one-click login as it really takes away the pain of double-opt-in registration forms and alike.

1.00 avg. rating (53% score) - 1 vote

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.