HWI OAuth Bundle – Custom Registration Form

I’ve been busy integrating a new application with third party / social authentication providers, specifically Tumblr, but this applies to pretty much any service as best I can tell.

In this post I want to cover how, and why, you can override the default implementation HWIOAuthBundle’s connect  configuration to specify your own account_connector.

I am using HWIOAuthBundle a little differently from how it is primarily designed to work.

HWIOAuthBundle is all about offering users of your site the ability to log in using their trusted third party / social media credentials rather than having to create yet another user account to use your site. You see this functionality a lot on the web these days, and it’s no surprise that Symfony has a bundle to deal with this.

Configuration of HWIOAuthBundle isn’t that hard, but the documentation doesn’t hand hold you as much as you might find with other bundle documentation. If you find yourself stuck or confused, the project issue tracker on Github is a great place to start digging.

Why Use A Custom account_connector ?

There are likely a good few reasons to do this.

In my case, I don’t want users to be able to register on the site with social media credentials. That in itself comes with its own set of headaches.

Users of my site must create an account the old fashioned way. This is handled through the brilliant FOSUserBundle.

Then, once authenticated, the user can connect  their account to one or more of the various social platforms – Twitter, Facebook, Google+ and what have you.

The curve ball in my situation comes from the above, but also that a User account can have many sub accounts. Think of it as in an agency looking after multiple client accounts, all from one login. None of the client accounts should be aware of the other client accounts under management, but the ‘master’ User account needs to seamlessly switch between them all.

The problem: when connecting to a new social account, how do you determine which client account to connect to which social account?

Out of the box, HWIOAuthBundle will return the ‘master’ User account object, and the social media site’s response object. From there, there is no simple way to determine the ‘active’ client account.

I didn’t want to start managing application state through a Session, as I could envisage a situation where a User may try and connect multiple accounts in multiple tabs, and end up with the wrong social media account assigned to the wrong client account. Messy.

Sending the relevant data via the URL seems like an easy solution to this problem. Perhaps though, not the right one.

Having given this a bit of thought, the next step seems to be having an input of type choice  on the connect page.

There is no bundle documentation (that I could find) to actually do this in their expected way (as in FOSUserBundle docs), though thankfully there is a handy Reference Configuration.

This is the relevant section:

# app/config.yml
hwi_oauth:
    # if you want to use 'connect' and do not use the FOSUB integration, configure these separately
    connect: ~
#        confirmation: true # should show confirmation page or not
#        registration_form_handler: my_registration_form_handler
#        registration_form: my_registration_form
#        account_connector: my_link_provider # can be the same as your user provider

As you can see, by default, we go with the built in / provided implementations.

We can override any of the default Services by specifying other Symfony Services, but exactly how these Services are configured is not explicitly described.

But what about overriding the template? Why isn’t that listed?

Well, that’s pretty common in Symfony, but it’s definitely a valid question.

Not one to turn down a challenge, I decided to do a little code diving, and see if I couldn’t come up with a DIY solution.

My Implementation

My idea is to have a modified connection form.

The connection form is displayed midway through the ‘connection’ process.

Remember, the User has already logged in to my application via their FOSUserBundle credentials.

Then, they can choose to ‘connect’ with a configured Social Media service – let’s say Tumblr, for sake of this example.

They click ‘Connect to Tumblr’, and are redirected to some URL on Twitter whereby they authenticate with their Twitter credentials (or, if already logged in, are simply shown a pop-up / modal to confirm they agree to this authorisation), and are then returned to my app with some params on the URL – their OAuth token info.

I want to save this profile info, but I need to ensure it is mapped to the correct client account under the currently logged in / locally authenticated User.

At this point, the User would have been redirected back from Tumblr to our ‘Connect’ form. Without any modification or styling, that form is going to look something like this :

hwio-oauth-connect-form

That form lives at :

/vendor/hwi/oauth-bundle/HWI/Bundle/OAuthBundle/Resources/views/Connect/connect_confirm.html.twig

We can tell it’s definitely this form by editing that template in the bundle.

I’m doing this for demo purposes, and it’s great for confirming what you already guess to be the right file, but don’t make direct edits to the bundle files (any of them), as all your changes will (potentially) be lost the next time you do a composer update on that particular bundle.

the-hwio-aiuth-connect-template-opened-in-phpstorm

Outputs:

hwio-oauth-bundle-slightly-modified-connect-formMy plan is to therefore add a checkbox on that page to allow the User to connect the given social media account to one or more of their customer / sub accounts.

We will need to handle some contingency stuff here, like that new User who doesn’t check any of the boxes and clicks ‘continue’ anyway. We don’t want this to cause an error on the back end, even though we would ideally validate this before hand on the front-end.

At this point I added in a way to stop the process before any persistence. I did this by defining my own CustomerConnector class and then turning this into a Symfony Service. This is where I would put all my implementation details:

<?php

// /src/AppBundle/OAuth/Connect/CustomerConnector.php

namespace AppBundle\OAuth\Connect;

use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class CustomerConnector extends BaseClass
{
    public function connect(UserInterface $user, UserResponseInterface $response)
    {
        dump($user);
        dump($response);
        exit('connect');
    }
}
# /app/config/services.yml
services:
    customer_connector:
        class: AppBundle\OAuth\Connect\CustomerConnector
        arguments: [@fos_user.user_manager, {}]
# app/config/config.yml
hwi_oauth:
    firewall_name: main
    connect:
        account_connector: customer_connector

This would let me go through the same process over and over, but die before anything further happens, showing me all the good stuff we would need to think about persistence.

One thing at a time.

Adding in the checkboxes

The next major hurdle is where to start with adding in our own Form implementation.

I’m not spotting a hugely easy way to achieve what we are trying to do without hacking a little.

The issue we have is that HWIOAuthBundle expects to be dealing with a User throughout the lifecycle of the connection process.

There is no easy way to override this – that I can see – and I don’t want to implement the 34 required methods of Symfony’s UserInterface on my Customer entities.

However, as it happens, the ConnectController doesn’t seem to care what we put on our form.

Good news, we have ourselves an entry point.

This means we can add extra stuff (basic HTML form elements) to the /vendor/hwi/oauth-bundle/HWI/Bundle/OAuthBundle/Resources/views/Connect/connect_confirm.html.twig  template.

But, rather than directly edit the template in the bundle – which we have already decided is a bad idea – we are going to use a little Symfony bundle inheritence magic to override the template with our own at:

/app/Resources/HWIOAuthBundle/views/Connect/connect_confirm.html.twig

This is a pretty cool Symfony feature, so if you haven’t used it before, check out the official documentation. It can come in handy for times like this.

Our template is going to replicate all the existing layout / functionality, but add in a little raw HTML to further our own goals:

{% extends 'HWIOAuthBundle::layout.html.twig' %}

{% block hwi_oauth_content %}
    <h3>{{ 'header.connecting' | trans({}, 'HWIOAuthBundle')}}</h3>
    <div class="row">
        <div class="span6">
            <p>{{ 'connect.confirm.text' | trans({'%service%': service | trans({}, 'HWIOAuthBundle'), '%name%': userInformation.realName}, 'HWIOAuthBundle') }}</p>
            <p>
            <form action="{{ path('hwi_oauth_connect_service', {'service': service, 'key': key}) }}" {{ form_enctype(form) }} method="POST" class="fos_user_registration_register">
                {{ form_widget(form) }}
                <input type="checkbox" name="accounts[]" value="value1"> value1
                <input type="checkbox" name="accounts[]" value="value2"> value2
                <div>
                    <button type="submit" class="btn btn-primary">{{ 'connect.confirm.submit' | trans({}, 'HWIOAuthBundle') }}</button>
                    <a href="{{ path('hwi_oauth_connect') }}" class="btn">{{ 'connect.confirm.cancel' | trans({}, 'HWIOAuthBundle') }}</a>
                </div>
            </form>
            </p>
        </div>
        <div class="span6">
            {% if userInformation.profilePicture is defined and userInformation.profilePicture is not empty %}
                <img src="{{ userInformation.profilePicture }}" />
            {% endif %}
        </div>
    </div>
{% endblock hwi_oauth_content %}

We’ve added in the raw HTML above, using two simple input elements to fake our data.

The basics are important – we have set the name=”accounts[]”  property on both, so that the responses are grouped together and we get an array result.

However, we still have a problem.

Even though we have added the checkboxes, and they will be being properly appended to our Symfony Request , the ConnectController  is not passing the Request object to our CustomerConnector :

// /vendor/hwi/oauth-bundle/HWI/Bundle/OAuthBundle/Controller/ConnectController.php#L209
$this->container->get('hwi_oauth.account.connector')->connect($currentUser, $userInformation);

I see this a lot, but remember, you can’t just ask for a Request object inside a Symfony Service in the same way you can in a Symfony Controller.

We do control this service. Whilst it may not directly obvious, this is actually referring to our account_connector.

We can’t change the method signature (connect(UserInterface $user, UserResponseInterface $response) ), and even if we could, that would mean we would also have to duplicate the entire ConnectController::connectServiceAction() , which we really don’t want to do.

Instead, we can use another bit of functionality available to us in Symfony: Setter Injection.

We can use setter injection to pass in Symfony’s RequestStack and then access the extra data direct from the Request object that way.

With all this configured, our CustomerConnector now looks like this:

<?php

namespace AppBundle\OAuth\Connect;

use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;

class CustomerConnector extends BaseClass
{
    /**
     * @var RequestStack
     */
    private $requestStack;

    public function injectRequestStack(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
    }

    public function connect(UserInterface $user, UserResponseInterface $response)
    {
        dump($user);
        dump($response);
        dump($this->requestStack->getMasterRequest());
        exit('connect');
    }
}
# services.yml

services:
    customer_connector:
        class: AppBundle\OAuth\Connect\CustomerConnector
        arguments: [@fos_user.user_manager, {}]
        calls:
            - [ "injectRequestStack", [@request_stack]]

Which looks like this on screen:

result-of-connect

We need to do a little modification to the template. The default translation text is incorrect, and we need to pass in the real data, but those are both quick Twig template tweaks.

We will likely want to add some Bootstrap specific (other front end design frameworks are available ;)) CSS to our form template also.

After that, we can move on to the logic that actually handles associating the oAuthToken data with our chosen accounts.

Review

I’m not 100% happy with this implementation.

That I’ve not had to overwrite any controller action is good.

But I now have to keep my template up to date with whatever implementation changes come in HWIOAuthBundle. Likely this won’t change hugely, but almost inevitably, at some point in the future, this will break in a hard to debug manner.

Also, I’m relying on the HWIOAuthBundle controller behaving as I expect it.

This feels quite shaky. Covering the code sufficiently with tests will be crucial, but the tests themselves will be slow, difficult to write, and brittle.

This approach is pragmatic. I am happy that this is the best solution to the problem given the time frame, and the fact that I am working on a personal project / MVP.

If you know of a better way to implement this, please do leave a comment.

Adding Tumblr to HWIOAuthBundle

Tumblr HWIOAuthI recently needed to add in Tumblr social authentication to a Symfony project. I made use of the excellent HWIOAuthBundle, which made the whole process a breeze.

Unfortunately, out of the box, there is no direct support for Tumblr.

However, no fear, as manually adding support in is simple enough.

Follow the installation instructions through to the end of Step 2. At the bottom of the section on Configuring Resource Owners is the option for ‘Others‘.

To get this to work, it’s mainly a bit of back and forth between the Tumblr API docs and your config.yml file, so let me spare you the time:

hwi_oauth:
    # list of names of the firewalls in which this bundle is active, this setting MUST be set
    firewall_name: main # important to change this to match the firewall you are adding hwioauth too
    resource_owners:
        tumblr:
            type:                oauth1
            client_id:           your_tumblr_api_client_id_here
            client_secret:       your_tumblr_api_client_secret_here
            access_token_url:    https://www.tumblr.com/oauth/access_token
            authorization_url:   https://www.tumblr.com/oauth/authorize
            request_token_url:   https://www.tumblr.com/oauth/request_token
            infos_url:           https://api.tumblr.com/v2/user/info
            scope:               "read"
            user_response_class: HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse
            paths:
                identifier:     response.user.name
                nickname:       response.user.name
                realname:       response.user.name

The paths section is interesting.

We must specify at least these three for HWIOAuthBundle to not error out.

What I’m doing here is using a dotted array access format. Kinda confusing sounding, but really what this is saying is : $response[‘user’][‘name’] .

Why the same data for each field?

Well, Tumblr doesn’t return much in the way of account data here.

You can see the full output from Tumblr here:

PathUserResponse.php on line 122:
array:2 [▼
  "meta" => array:2 [▶]
  "response" => array:1 [▼
    "user" => array:5 [▼
      "name" => "futuristicallycrookedbread"
      "likes" => 0
      "following" => 6
      "default_post_format" => "html"
      "blogs" => array:1 [▼
        0 => array:29 [▼
          "title" => "Untitled"
          "name" => "futuristicallycrookedbread"
          "posts" => 4
          "url" => "http://futuristicallycrookedbread.tumblr.com/"
          "updated" => 1441562102
          "description" => ""
          "is_nsfw" => false
          "ask" => false
          "ask_page_title" => "Ask me anything"
          "ask_anon" => false
          "followed" => false
          "can_send_fan_mail" => true
          "is_blocked_from_primary" => false
          "share_likes" => true
          "likes" => 0
          "twitter_enabled" => false
          "twitter_send" => false
          "facebook_opengraph_enabled" => "N"
          "tweet" => "N"
          "facebook" => "N"
          "followers" => 0
          "primary" => true
          "admin" => true
          "messages" => 0
          "queue" => 0
          "drafts" => 0
          "type" => "public"
          "subscribed" => false
          "can_subscribe" => false
        ]
      ]
    ]
  ]
]

This is the result of using Symfony’s VarDumper component on line 122 of PathUserResponse.php .

How did I know to add the dump($response); statement in there?

Well, we specified in the config.yml file that we are expecting a User Response to be:

user_response_class: HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse

I just opened up that file and added in the dump, using the $response variable that had been defined earlier on line 113.

There is a little bit of extra config in security.yml :

security:
    providers:
        social_user_provider:
             id: oauth_user_provider

    firewalls:
        main:
            anonymous: ~
            oauth:
                resource_owners:
                    tumblr:        "/login/check-tumblr"
                login_path:        /login
                use_forward:       false
                failure_path:      /login
                oauth_user_provider:
                    service: oauth_user_provider

    access_control:
        - { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/connect, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, role: ROLE_USER }

Alongside the other HWIOAuth routes, you will need to add in one for Tumblr:

# app/config/routing.yml - or where ever you are storing routes
tumblr_login:
    path: /login/check-tumblr

The oauth_user_provider  service must exist, so here is my definition:

# services.yml (or where ever)
services:
    oauth_user_provider:
        class: AppBundle\Model\OAuthUserProvider

This should work for other social auth providers, with only a little modification. As much as this looks like loads of config (hey, it’s a Symfony project!), it’s actually really pretty easy to add in new social providers. I was pleasantly surprised.

node-gyp rebuild Error on Ubuntu 15.04 with Node 4.x.x

I’m going to start with the tl;dr version:

if you are experiencing this issue, and you have recently installed Node 4.x.x, then this is likely your issue.

Solutions?

Find another package, or use an older version of node for now if you absolutely must use that package.

The Longer Version

I had been trying to install “webkid-react-starterkit@0.3.1” on my Ubuntu dev box for ages last night, and spent a wasted 25 minutes again on it tonight.

The error was staring at me right in the face honestly:

gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/usr/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:270:23)
gyp ERR! stack     at emitTwo (events.js:87:13)
gyp ERR! stack     at ChildProcess.emit (events.js:172:7)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:200:12)
gyp ERR! System Linux 3.19.0-30-generic
gyp ERR! command "/usr/bin/nodejs" "/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /home/chris/Development/php/dunglas-api-test/react-example/node_modules/jest-cli/node_modules/jsdom/node_modules/contextify
gyp ERR! node -v v4.2.1
gyp ERR! node-gyp -v v3.0.3
gyp ERR! not ok 
npm ERR! Linux 3.19.0-30-generic
npm ERR! argv "/usr/bin/nodejs" "/usr/bin/npm" "install"
npm ERR! node v4.2.1
npm ERR! npm  v2.14.7
npm ERR! code ELIFECYCLE

npm ERR! contextify@0.1.14 install: `node-gyp rebuild`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the contextify@0.1.14 install script 'node-gyp rebuild'.
npm ERR! This is most likely a problem with the contextify package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     node-gyp rebuild
npm ERR! You can get their info via:
npm ERR!     npm owner ls contextify
npm ERR! There is likely additional logging output above.

npm ERR! Please include the following file with any support request:
npm ERR!     /home/chris/Development/php/dunglas-api-test/react-example/npm-debug.log

It’s likely not as easy to see here, but on my terminal output, it’s colour coded and it’s rather glaringly obvious:

npm ERR! Failed at the contextify@0.1.14 install script 'node-gyp rebuild'.

I went round many houses.

From trying to install contextify globally, to completely reinstalling node, to even getting down to specifying the exact version of the v8 profile I wanted (npm install v8-profiler@3.6.2-1 ).

Nothing worked.

Anyway, it turns out it’s incompatible with this version of node all along.

NodeJS 4.0.0 only tagged as stable on 8th September 2015, and here I am running 4.2.1.

Buckle in, it’s going to be a wild ride.

How I “fixed” Segmentation Fault in Codeception 2.1.3

I hit on a bug when upgrading from Codeception 1.8.x to Codeception 2.1.3.

I entirely skipped the 2.0.* branch of Codeception, not through any other reason than 1.8.x was working fine for me.

During a recent site update, I decided it would be a good time to update the Codeception tests suite to the latest release.

Unfortunately, the upgrade did not go smoothly.

crvuser@crv-dev:/var/www/codereview.dev$ php vendor/codeception/codeception/codecept run acceptance SignUpCest.php --debug
Codeception PHP Testing Framework v2.1.3
Powered by PHPUnit 4.8.12 by Sebastian Bergmann and contributors.
Segmentation fault

I constantly hit this issue, but only with the Acceptance test suite. The unit tests passed absolutely fine.

I even got very specific with my versions inside composer.json:

    "require-dev": {
        "phpunit/phpunit": "4.8.12",
        "codeception/codeception": "2.1.3",
        "facebook/webdriver": "1.0.2"
    },

(some other stuff removed for brevity)

But still I hit the Segmentation Fault error every single time I ran the acceptance tests.

My Fix

Alas, my fix isn’t great. But it has worked.

I figured with the major version changing, chances are something went pear shaped internally.

My test suite isn’t hugely customised, but it did have a bit of extra stuff sprinkled in here and there. Something in there may very well have caused it.

Anyway, after much debugging (difficult as it happens), I gave up and did the following:

  1. Renamed the tests/ dir to tests_bak
  2. Renamed codeception.yml dir to codeception_bak.yml
  3. Ran php vendor/codeception/codeception/codecept bootstrap

And then, piece by piece, copied all my tests and config back across to the respective files.

Don’t copy / overwrite.

Instead, copy the relevant bits from the files – especially the configs. Things have changed.

Also, I found it easiest to re-run the test suite for every individual file I copied over. One by one at first, but as I copied in more of the directories, I ended up getting quicker and quicker.

Not the most elegant of solutions, but the test suite is back to working.

Interested in learning more about Codeception? I have a full course on the site.

Ahh, but it returns…

I wanted to add something extra to this.

I hit this issue again in Codeception 2.1.3.

From what I can discern, the size of your test database (e.g. dump.sql ) seems to have some impact on causing the Segmentation Fault issue.

I found that by removing data from my tables – generally less than 50 rows per table – the Segmentation Fault issue went away.

Frustrating that I don’t know the exact cause of it, but if in doubt, strip it out.