Better NodeJS OAuth example

Back when I was writing the Node-RED Alexa Home Skill node I used an example oAuth setup I found online here.

This worked for the initial setup but I couldn’t get Alexa to renew the oAuth tokens, as a temporary work around I set the token life to be something huge (300 years…). Again this worked, but it’s not the best and even thought I’m not likely to be around in 2318 to worry about it having crazy long token expiry times negates some of the benefits of the oAuth system.

I spent a couple of weeks bouncing email back and forth with the Alexa team at Amazon trying to work out what the problem was and I thought it would be useful to write up the findings to make life easier for anybody else wanting to use NodeJS/Passport to implement an Alexa Skill. I created a separate stripped down minimal skill to work through this without having to mess with the live skill and disrupting users and the whole thing is up on github here, but I’m going to walk through the changes here.

Let’s start by looking at what we had to start with, the following snippet of code is the part that returns a oAuth token to the remote service (in this case Amazon Alexa) when the user authorises it to use my service.

server.exchange(oauth2orize.exchange.code({
  userProperty: 'app'
}, function(application, code, redirectURI, done) {
  GrantCode.findOne({ code: code }, function(error, grant) {
    if (grant && grant.active && grant.application == application.id) {
      var token = new AccessToken({
        application: grant.application,
        user: grant.user,
        grant: grant,
        scope: grant.scope
      });
      token.save(function(error) {
        done(error, error ? null : token.token, null, error ? null : { token_type: 'standard' });
      });
    } else {
      done(error, false);
    }
  });
}));

This returns the absolute bare minimum (the 2 fields marked as required in the spec) for the oAuth spec.

{
  "access_token": "a1b2c3d4e5g6......",
  "type": "standard" 
}

The first fix is to add refresh token that can be used request a new token. To do this we need to add a new model to store the refresh token and it’s link to the user.

var RefreshTokenSchema = new Schema({
	token: { type: String, unique: true, default: function(){
		return uid(124);
	}},
	user: { type: Schema.Types.ObjectId, ref: 'Account' },
	application: { type: Schema.Types.ObjectId, ref: 'Application' }
});

And now to create a refresh token and add it to the token response from earlier.

server.exchange(oauth2orize.exchange.code({
  userProperty: 'appl'
}, function(application, code, redirectURI, done) {
  OAuth.GrantCode.findOne({ code: code }, function(error, grant) {
    if (grant && grant.active && grant.application == application.id) {
      var token = new OAuth.AccessToken({
        application: grant.application,
        user: grant.user,
        grant: grant,
        scope: grant.scope
      });
      token.save(function(error) {
        var refreshToken = new OAuth.RefreshToken({
          user: grant.user,
          application: grant.application
        });
        refreshToken.save(function(error){
          done(error, error ? null : token.token, refreshToken.token, error ? null : { token_type: 'standard' });
        });
      });
    } else {
      done(error, false);
    }
  });
}));

OK, so now we have a token response that looks like this.

{
  "access_token": "a1b2c3d4e5f6......",
  "type": "standard",
  "refresh_token":  "6f5e4d3c2b1a...."
}

This was basically what I was using when I launched the service, it has all the required fields and a refresh token. Amazon’s Alexa Smart Home API has an explicit error to return when a token has expired, so with that in mind I had assumed that when I return that error then the service would use the refresh token to get a new token. This assumption turned out to be wrong, even if you explicitly tell Amazon that the token has expired it won’t try to refresh it unless it is after the expires_in time in the token response… Now expires_in is listed as optional (but recommended in the spec) but it turns out that Amazon interprets a missing expires_in as tokens having an infinite life and as such will NEVER renew the token. To fix this we need to include an expires time. An expires time is already in the token model for the database (remember I’d already edited this to be 300 years) so we just need to get it included in the token response.

var expires = Math.round((token.expires - (new Date().getTime()))/1000);
refreshToken.save(function(error){
  done(error, error ? null : token.token, refreshToken.token, error ? null : { token_type: 'standard',  expires_in: expires});
});

Which finally gets a token response like

{
  "access_token": "a1b2c3d4e5f6......",
  "type": "standard",
  "refresh_token":  "6f5e4d3c2b1a....",
  "expires_in": 7776000000
}

This worked, the first time the token expired (in 90 days) but not the second time, because I’d not included a expires_in the token response when the token was refreshed.

As I said at the start all the code for a bare bones implementation is on github here, there are a couple of other changes, e.g.to make the system reuse existing tokens if they are still valid if the user removes and adds the skill to Alexa, to change the token type over to Bearer and including the scope information in the token response just to be complete. Should I ever get enough free time to work out how to get the model to work for a Google Assistant version of the Node-RED node this code will form the basis as that also needs an oAuth based service.

The quest for a IPv6 capable mobile data plan

For the last few weeks I’ve been trying to find a UK Mobile data provider that will provide a IPv6 address (well, hopefully a bunch of them that I can share round a few devices, but given how IPv6 normally works this should be trivial).

The reason I want this is because I’m playing with VoIP and SIP at home and I want a reliable way to be able to do direct point to point routing without having to resort to a VPN constantly running on my test devices. While my ISP (the wonderful A&A) have recently started handing out /29 and /30 subnets to make this sort of thing easier for IPV4 most mobile providers don’t provide a routable IP address, they all use CGNAT.

Currently the only major player that claims to support IPv6 is EE. I had a quick search online and found a bunch of forum posts from mid 2017 saying that they had started to roll it out, but only to new pay monthly customers. Given it is now approaching mid 2018 I thought things must have moved on a little but I couldn’t actually find anything more up to date anywhere on line. Having poked around on EE’s website none of the plan information mentions IPv6.

I called into one of EE’s retail stores and had a chat with the staff who didn’t really understand what I was asking for (to be fair it is a bit of a technical question compared to what they normally get asked), but I did manage to convince one of them to disconnect from the WiFi and get android to list their addresses. This showed a IPv6 address so things were looking up.

At a bit of a loss I called EE’s customer service team to see if they could tell me which plan I should pick, the Level 1 agent couldn’t help so passed me to Level 2, unfortunately they weren’t much help either and the best they could suggest was to get hold of a SIM and try.

Since the EE website offers sim cards for free I decided to try and order one and give it a go. At which point I ran into the next problem, the order form is not RFC2822 compliant. Meaning that it will not allow you to include tags in email addresses e.g. foo+ee@example.com where the +ee is a tag allowing you to identify who you gave the email address to.

After a little back and forth with EE’s social media team they managed to arrange to send me the Pay & Go Data (Tablet & 4GEE WiFi) SIM I was trying to order and hopefully pass to issue on to their web development team (to be fair validating email addresses is near impossible, which is why you shouldn’t even try).

Given this was explicitly a data SIM I was hopeful it would get a usable address. After topping up £10 to activate the sim and using that to buy £5 200mb data bundle I fired things up and crossed my fingers. And no joy, so back to having to run VPN tunnels on all my devices to effectively put them on my home network.

In conclusion is the IPv6 is basically still not available to the UK mobile data market.