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:

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 :

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:

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:

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:

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 :

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:

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.

Shares

Published by

Code Review

Code Review

CodeReviewVideos is a video training site helping software developers learn Symfony faster and easier.

Leave a Reply

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