Jwt Authentication with Ember + SailsJs / Waterlock

Thought I'd knock together a little example of using Waterlock/SailsJs with EmberJs and using JWT as the mechanism.

JSON Web Token (JWT) is a JSON-based open standard (RFC 7519) for passing claims between parties in web application environment. The tokens are designed to be compact, URL-safe and usable especially in web browser single sign-on (SSO) context.
JWT is a means of providing a simple claims based authentication between a client and a server. The token is an encrypted piece of data which can be sent as part of a payload to allow access to restricted services. The full RFC is here https://tools.ietf.org/html/rfc7519

The basic gist of JWT is this:

  1. User authenticates with server (identity/password, oAuth etc)
  2. Server generates JWT using secret key and some payload (usually a JSON string that provides some basic identification information, a subset of the User object for instance)
  3. Server responds with JWT access_token
  4. Client stores access_token in cookie or local storage (we will be using local storage)
  5. Client provides access_token in future requests.
While this may look a lot like the standard cookies session ID flow there is a major difference in that a JWT is a small encrypted package which defines the access claim and identifies the user. They are inherently much more secure than cookies as well as allowing statelessness - and this is the big one. Yeah sure we can manage session state across servers in a cluster using a central cache like Redis. Go ahead, hope you have a nice day breaking the fundamental design of the web. 


Waterlock is a nifty little user authentication//JSON web token management tool built for Sails:
Using these two projects in tandem makes it ridiculously easy to get up and running with JWT and a decent REST API. Couple that with EmberJs - "a framework for ambitious web applications" and you have a pretty niffty set-up.

The best way for handling auth in an Ember app is through the use of Ember Simple Auth:
While all of these parts are really simple to use they aren't necessarily that simple to get up and running with. This whole area of software development (JavaScript in particular) is evolving at a rate of knots,  JWT is a relatively new technology and server side frameworks such as SailsJs and client side frameworks like EmberJs are moving so fast it's really, really hard to keep up unless you're lucky enough to be able to dedicate your full attention! This is both good an bad news, there's a lot of well meaning blog posts out there explaining how to do XYZ with these tools but as soon as they are published they're out of date. I expect the same will unfortunately be true of this post. But for now at least the repo is using the very latest of each of the dependancies and (I hope) provides some clear guidance in getting this set up.

So that's the server side and UI components. I'm not going to go through setting up each of these components - you can see how that's all done on each of their repos. I'm also asuming that if you're interested in this little cocktail of code you're probably aware of each of them but a little confused as to how you go about pinning it all together.

So down to the code! The repo is here: https://github.com/wayne-o/ember-waterlock-example go ahead and clone it if that's what flicks your switch - I expect it does else you wouldn't have read this far.

There's 2 parts to this repo:
  • The Server
  • The Client
I'll walk us through the interesting parts of the server initially - there's not much to it!

I've added a couple of extras here that help make things simple. Ember-Data is a brilliant little library - it doesn't do everything and I don't use it everywhere but where it is used it's very clever and ahead of it's time. One of those areas is being a pretty complete implementation of the JSON-API spec: http://jsonapi.org/. And to get SailsJs dancing nicely with this spec we need to update the blueprints. To do this I use the brilliant repo by mphasize: https://github.com/mphasize/sails-ember-blueprints. What this does is turn the standard JSON output from sails into nice neat JSON API compatible JSON. Neat.

I've also included waterlock and waterlock-local-auth. To get all these you just need to cd into the /sonatribe-api directory and run
npm install
Now - that's pretty much it - if you're doing these from fresh you'll need to run the generate blueprints command but that's all documented on the repo's readme.md - RTFM ;)

The noteworthy config changes include in the /config/models.js file:

https://github.com/wayne-o/ember-waterlock-example/blob/master/sonatribe-api/config/models.js

You can see that I've added the "associations" and "validations" nodes to the config. It just defines explicitly how you want these types of nodes to appear in the JSON output.

Next up is waterlock.js:

https://github.com/wayne-o/ember-waterlock-example/blob/master/sonatribe-api/config/waterlock.js

You're going to need to tell it to pluralizeEndpoints and also you need to rename the token name - ember-simple-auth expects "access_token" - don't do this and you're into a world of pain. You also want to set "stateless" to false - we don't want any nasty sessions on the server thank you please. Sessions are grotty at the best of time and mess with your head when you try and step up in the scalability realm. JWT is stateless authentication. It keeps things clean.

That's it for the API! Simple yeah.

So now onto the client - this is where things can get a little... involved. But considering what you're getting - I'd say not too involved. You'll want to cd into the /sonatribe-ui folder and do the usual npm install && bower install stuff.

I've included ember-cli-simple-auth and ember-cli-simple-auth-token which are set up in the /config/environment.js file: https://github.com/wayne-o/ember-waterlock-example/blob/master/sonatribe-ui/config/environment.js

Go with my defaults for now - you can mess with these later if you want.

Things to note:
  • I've used pod structure - it's great for organising an app small or large.
  • I've used the new computed decorators: https://github.com/rwjblue/ember-computed-decorators meaning we get Java style attribute decoration for things like @observe and @property and even @whateverYouWant
All this works out of the box when you npm/bower install my repo. The login and register components can be found here https://github.com/wayne-o/ember-waterlock-example/tree/master/sonatribe-ui/app/pods/components - we let the framework do the work so these components are ridiculously simple:
The only noteworthy thing being the slightly different flow for registering as opposed to loging in - waterlock (currently) uses the same workflow and if an un-registered user attempts to login and "createOnNotFound" (https://github.com/wayne-o/ember-waterlock-example/blob/master/sonatribe-api/config/waterlock.js#L48) is true, waterlock assumes you want to create a new users - and provided the dupe checking passes (no duplicate username/email validations) - a new user is created. In future versions of Waterlock there will be an explicit /register endpoint. For now it's best to query the /users endpoint for email / username availability. I'm doing this here: https://github.com/wayne-o/ember-waterlock-example/blob/master/sonatribe-ui/app/pods/components/register/register-panel/component.js#L10 (not wired up atm - I'll likely do that in the week to finish up).

So the process is to call /login if the validations for username / email pass - get the user registered and logged in and then, using the response with our authentication data we can populate the users User model and save. Finally we push the user off to the main app by sending an action to the parent route ("userAuthenticated").

I'm aware that because I am accessing Ember-Data and JQ.Ajax within this component I am completely side stepping DDAU (http://www.samselikoff.com/blog/data-down-actions-up/) - and this is a bit cringe but I'm going to leave it as is as I want the example to be clear. The simplicity of using these libraries side by side should be unhindered!

Comments