Wednesday, September 23, 2015

Jwt Authentication with Ember + SailsJs / Waterlock Part 2 - Google oAuth2

Leading on from part 1 Jwt Authentication with Ember + SailsJs / Waterlock I decided to continue with the theme by adding the ability to authenticate with a 3rd party authentication service - in this example I'm using Google oAuth2.

Now this wasn't simple for me to figure out initially! Partly because, in my mind, the confusing docs surrounding Google oAuth2 where they state:
When a user grants access to your app for a particular scope, the user is looking at the user consent screen, which includes project-level product branding that you set up in the Google Developers Console. (For information about setting up the consent screen, see Setting up OAuth 2.0 in the Developers Console help.) Therefore, Google considers that when a user has granted access to a particular scope to any client ID in a project, the grant indicates the user's trust in the whole application for that scope.
Now, I read that as: when you collect an authentication code from one medium within your applications "product" suite, provided other parts of that suite use a client ID that share the same "project" at Google's end - then you can use that client and authentication code to gain an access token - and thus validate the user. Apparently not so! I spent ages going round in hoops over this and settled on a simple setup which means I can authenticate the user on both the client (Ember) and server (Sails).

To make things clear - the idea of all this is to provide a sensible UX for a browser application to authenticate using 3rd party auth services. This includes having the auth flow inside the application and as we are an SPA we don't want to have any nasty page refreshes or redirects (in the main browser). I am pulling in a few other libraries to achieve this and have had to modify the Waterlock library slightly to enable this to work. I'll be cleaning that up and pushing back to Waterlock soon but for now I have included the deps as submodules inside our repo.

The desired flow is:

  1. User clicks "authenticate with Google" button
  2. Popup appears with Google's auth flow
  3. User provides access to our app 
  4. Popup closes (main app never refreshes or redirects)
  5. Our app receives the authorization code from Google
  6. Sends that code to our API
  7. Our Api then validates that code by exchanging it for an access token
  8. If the code is valid we find or create a user and set up the JWT for the client app
  9. Our client app receives the JWT and uses that for future requests to the server
Quite long winded but the net result is a great UX for our users and a reliable authentication flow that's secure. Now - one major change that I have had to make here to make this work - baring in mind that I have no idea how to get "Cross Client Authorization" to work - was to change the way the app access the API. To enable this I am proxying the API through ember. You can achieve this by running the following command (instead of just running "ember server"):
ember server --proxy http://localhost:1337

In terms of changes - I have included  a modified version of waterlock-google-auth in the API - this has a simple setup in /config/waterlock.js - you can see the required bits in my repo and by reading the readme on the main repo. The changes I had to implement were to ommit the requirement of the client to provide a CSRF - I may add a step where the client can retreieve one before starting the auth flow - I may omit it by using a separate endpoint for SPA auth - not sure of the implications yet. That's all for the server!

The client has a few new pieces:

Because we are customizing the standard flow a tad (sending the auth code to the API) I had to implement a custom authenticator:

The interesting part here is where I exchange the auth code for a validated user with an access token provided by our server - this makes up our JWT! Nice.

I'll likely move on to add Facebook authentication and possibly create our own Spotify add-on to Waterlock and wire that in. Finally I'm planning on adding web sockets to provide "live" UI updates.

No comments:

Post a Comment