Noted: Let’s make an app: part 3

Week three was… tough

Where did I start?

  • Electron app, with installers for Windows and Mac OS
  • Android app
  • IOS app
  • Hosted Node.JS backend, using socket.io to manage socket/long-polling connections with all the clients above
  • Authentication/Authorization handled by connecting to Auth0’s identity as a service stuff

What were my goals?

  • Actually understand what I’ve implemented for Authentication/Authorization
  • Flush out any bugs in the system
  • Start moving towards app stores/distributing installers for the desktop apps

What did I do?

  • I spent half the week buried in the OAuth 2.0 and OpenId connect specifications, which was painful, but worth doing.

  • I made a sweet logo.

  • I started sniffing around app stores, and realised that in order to actually get something distributed to app stores, that isn’t buggy, I need to scale back a bit on my ambitions. Rather than supporting lots of different clients, I need to get one/two clients working really reliably, both online and offline.

What did I learn?

The first half of the week was dedicated to OAuth 2.0.

This is what I know now that I didn’t know before:

OAuth came about because of a need to avoid things like this:

It’s a way of allowing users to consent to giving access to some of their data held by one provider, to another provider, without giving access to everything. It allows application A to direct users to application B, where they can sign in to application B and give consent to share some of the data held by application B with application A.

So in an OAuth flow, there are typically three actors. In our case, we’re going to call them Bob, Application A, and Application B.

Bob – uses Application A, and Application B. He has an account with Application A, and at some point added his contact details to it, that is his name, and email address, as well as some personal information.

Application A – has some stored information on Bob:

{
  Users: [
   { Bob: 
     {
       name: 'Bob Cratchet',
       email: 'bobster666@aol.com',
       faveColor: 'Vermillion',
       faveFood: 'Tacos'
     }
  }]
}

Application B wants to know Bob’s email address, full name, and favourite colour, but they want to get it from Application A, rather than asking Bob for it directly.

Application A knows who Bob is, and is able to confirm, based on some information Bob holds (username and password for instance), that Bob is Bob.

Application A also has some data about Bob, namely his full name, email address, favourite colour and favourite food.

Application A should not just share this data with anyone, they should only do so with Bob’s consent.

So, Bob goes to Application B’s website/app/client of some sort, and starts using it. At some point, Application B says to Bob,

Hey you have an account with Application A, would you like to share some information from Application A with us? We’d like to know who you are, and what your favourite colour is, and Application A knows that already. If you do then we’ll be able to reflect your preferences in our site/app/client of some sort by turning it your favourite colour!

Bob is like

shit yeah I really want to see Application B in my favourite colour‘.

Application B sends Bob to a page that Application A has prepared on their domain for just this purpose. Bob signs in to this page, by providing his username and password, so that Application A knows who he is. Application A then says something along the lines of

Hey Bob, Application B says they want to see your name, email address and favourite colour, are you cool with that? We won’t let them see anything else, and you can revoke their access whenever you want

Bob again is like

shit yeah I really want to see Application B in my favourite colour

Because Bob agreed to give access to his data, Application A then sends a special code to Application B.

Application B can’t use this code to get Bob’s favourite colour yet, because Application A can’t be sure that the code they sent hasn’t be nabbed by some other nefarious entity in transit.

In order to access Bob’s favourite colour, Application B has to confirm who they are with Application A, normally via some sort of shared secret.

However it is implemented, Application B has to be able to prove to Application A that they are in fact Application B. Once they have done that, they can exchange their special code which says ‘Bob says application B can see his favourite colour and stuff‘, for a special token.

This token can then be used by application B (who have proved who they are), to get Bob’s data from Application A.

Which is pretty neat.

In order for this system to work, there need to be reliable ways of proving that each of these actors are who they say they are, before passing anything sensitive to them.

These ways of figuring out who everyone is are where a lot of the technical complexity comes in.

Depending on where Application B is accessed from, the steps vary quite a bit.

In order to avoid this post becoming monstrously long, I’m just going to detail what happens when you have loud mouthed native clients that can’t keep a secret.

In my case, I will have a native mobile client, and a native desktop client, and I want to control access to an API on a separate domain, by ensuring that the user that authenticates with Application A via any of the clients above, is only allowed access to their own notes.

The tricky bit with these clients is knowing how to trust that they are who they say they are.

It is easy to give them the special code (the one which can be exchanged for a special token which actually gives them access to whatever resource we are interested in), however how do we prove that they are who they say they are?

If Application B is a server side web application, this is easy. When Application B registers with Application A, they agree on a secret, which only they know. Application B can keep this secret safely in the server, as it can’t be accessed by other people, and then just send this along with its special code. Then Application A will be like

ah yeah I know this guy, here have your token

Native and mobile applications on the other hand are deeply untrustworthy. Any secret they are given access to can be pulled out of the code, making it kind of pointless to give them a secret at all.

Luckily there is a solution:

PKCE (pixie) to the rescue!

https://tools.ietf.org/html/rfc7636

Each of the different ways of managing the OAuth process have different names. The one you should use for mobile/native clients is called ‘Authorization Code Flow with Proof Key for Code Exchange’

PKCE stands for Proof Key for Code Exchange, and it is a way for a public, untrustworthy client, to authenticate themselves with Application A.

To implement it, your leaky client has to provide a code_verifier, and a code_challenge.

In a JavaScript application, you can do this like so:

const crypto = require("crypto")

function base64URLEncode(str) {
  return str
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");
}

function sha256(buffer) {
  return crypto.createHash("sha256").update(buffer).digest();
}

var verifier = base64URLEncode(crypto.randomBytes(32));

var challenge = base64URLEncode(sha256(verifier));

The verifier is a base64 encoded randomly generated value which is difficult to guess.

The challenge, is a hash of the verifier.

These are dynamically created, and so there is no static value that can be pulled out of the code, unlike with a static secret.

When Application B sends the user off to Application A to log in etc, they also send the code_challenge, which is a hash of the randomly generated verifier value.

Then, when Application B needs to authenticate themselves with Application A, they send the code_verifier, which is the original randomly generated value.

Application A can then hash the code_verifier value, using the same hashing algorithm that Application B did when they created the challenge, and check that they get the same result.

This then means that Application A can be pretty sure that Application B is in fact Application B, and that they are the same instance of Application B that asked for access to Application A’s data earlier.

Again, very neat.

Securing access to notes:

The above section kind of explains how my various clients prove that they are who they say they are, however, it doesn’t cover how my backend notes API uses that information.

I needed to make sure that only Bob can access his notes, which are delivered to him via a socket.io connection.

In order to do that I added my API to the API section of my Auth0 dashboard, and my clients to the Apps section of the dashboard.

I think this means that in the example above, my Auth0 instance/’tenant’ (their words) becomes a layer on top of Google/Email authentication, and essentially becomes my gateway to control access to my API. They are responsible for issuing access tokens that can be used to access my API.

Auth0 handles authenticating the user, either via their own hosted email/password database that you get when you use their service, or via a 3rd party (Google in my case). Once they are authenticated, Auth0 creates a JWT access token, signed with their private key, and sends it along with any information you have requested, using the flow detailed above.

Once the client has its access token, it sends it to my notes server to prove to my server that Bob is Bob.

In my case, so long as I can be sure that the user is who they say they are, they can access their notes. I don’t require any more information than that.

Because I am using sockets to deliver notes, rather than just requesting data via REST endpoints, my steps look like this:

CLIENT:

1) Authenticate Bob, via Google, or email login, in return for an access token (handled by Auth0).

2) Establish socket connection with notes server

3) Send access token via custom socket event:

    socket.emit("authenticate", { token });

4) If the socket connection is closed for any reason, start at step 1 again.

SERVER

1) On connection event, wait 15 seconds for a second ‘authenticate’ event, with a JWT access token. If no ‘authenticate’ event, terminate connection.

2) On receiving an ‘authenticate’ event, with a JWT access token, verify that it was signed by Auth0 and is valid.

3) If it is valid, give the user access to the socket.io room corresponding to their username, as verified by Auth0, otherwise close the connection.

4) Set a timeout and close the connection once the expiry time in the JWT access token is reached.

Refresh tokens

The other thing I had to wrap my head around was refresh tokens.

These are long lived tokens that can be exchanged, without redoing the initial OAuth steps, for another access token.

Because they are pretty powerful tokens, they have to be stored securely on the client device in question.

I have ended up using rotating refresh tokens, which means that after the initial OAuth steps (Authorization Code Flow with Proof Key for Code Exchange), the client just requests new access tokens when the old one runs out, with their securely stored refresh token. When the access token is returned to them, they also get a new refresh token, and the old one is invalidated.

The access tokens themselves don’t last very long.

The reason for using this system is to allow users to benefit from not having to log in all the time, while maintaining a reasonable level of safety, in that it minimises the potential nasty effects of someone somehow getting hold of an access token or a refresh token, because they are only valid for a small window.

What now?

I really want a finished product, so I need to descope some stuff. I’m out of proof of concept mode, and into minimal viable product mode.

Android is out, Windows is out.

IOS is in, as is, potentially, a native Mac OS client (as opposed to Electron).

To get the product to a stage where it is actually useful, it needs to handle situations where there is no/patchy network access better, and also needs a lot of polish.

Next week the focus is navigating Apple’s distribution channels (App Store), coming up with a strategy for offline vs online notes, getting the IOS app polished and ready for distribution, and potentially starting the Mac OS native client.

This week was tough but productive, I hope next week will be similar.

Leave a Reply

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